Import Data

corrcounts_merge <- readRDS("~/VersionControl/senescence_benchmarking/Data/corrcounts_merge.rds")
metadata_merge <- readRDS("~/VersionControl/senescence_benchmarking/Data/metadata_merge.rds")
SenescenceSignatures <- readRDS("~/VersionControl/senescence_benchmarking/CommonFiles/SenescenceSignatures_divided_newCellAge.RDS")
library(markeR)
library(ggplot2)
library(ggpubr)
library(edgeR)
Loading required package: limma
?markeR
ℹ Rendering development documentation for "markeR"

Scores

?CalculateScores
ℹ Rendering development documentation for "CalculateScores"
df_ssGSEA <- CalculateScores(data = corrcounts_merge, metadata = metadata_merge, method = "ssGSEA", gene_sets = SenescenceSignatures)
Considering unidirectional gene signature mode for signature [DOWN]_CellAge
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering unidirectional gene signature mode for signature [DOWN]_HernandezSegura
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering unidirectional gene signature mode for signature [DOWN]_SeneQuest
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering unidirectional gene signature mode for signature [UP]_CellAge
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering unidirectional gene signature mode for signature [UP]_HernandezSegura
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering unidirectional gene signature mode for signature [UP]_SeneQuest
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering unidirectional gene signature mode for signature CSgene
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering unidirectional gene signature mode for signature GOBP_CELLULAR_SENESCENCE
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering unidirectional gene signature mode for signature GOBP_NEGATIVE_REGULATION_OF_CELLULAR_SENESCENCE
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering unidirectional gene signature mode for signature GOBP_POSITIVE_REGULATION_OF_CELLULAR_SENESCENCE
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering unidirectional gene signature mode for signature REACTOME_CELLULAR_SENESCENCE
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering unidirectional gene signature mode for signature SAUL_SEN_MAYO
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
senescence_triggers_colors <- c(
  "none" = "#E57373",  # Soft red  
  "Radiation" = "#BDBDBD",  # Medium gray  
  "DNA damage" = "#64B5F6",  # Brighter blue  
  "Telomere shortening" = "#4FC3F7",  # Vivid sky blue  
  "DNA demethylation" = "#BA68C8",  # Rich lavender  
  "Oxidative stress" = "#FDD835",  # Strong yellow  
  "Conditioned Medium" = "#F2994A",  # Warm orange  
  "Oncogene" = "#81C784",  # Medium green  
  "Lipid Accumulation" = "#E57373",  # Coral  
  "Calcium influx" = "#26A69A",  # Deep teal  
  "Plasma membrane dysruption" = "#D32F2F",  # Strong salmon  
  "OSKM factors" = "#FFB74D",  # Bright peach  
  "YAP KO" = "#9575CD"  # Deep pastel purple  
)

cellTypes_colors <- c(
  "Fibroblast" = "#FF6961",   # Strong Pastel Red  
  "Keratinocyte" = "#FFB347", # Strong Pastel Orange  
  "Melanocyte" = "#FFD700",   # Strong Pastel Yellow  
  "Endothelial" = "#77DD77",  # Strong Pastel Green  
  "Neuronal" = "#779ECB",     # Strong Pastel Blue  
  "Mesenchymal" = "#C27BA0"   # Strong Pastel Purple  
)

cond_cohend <- list(A=c("Senescent"), # if no variable is defined, will be the first that appears in the ggplot
                    B=c("Proliferative","Quiescent"))

PlotScores(ResultsList = df_ssGSEA, ColorVariable = "CellType", GroupingVariable="Condition",  method ="ssGSEA", ColorValues = cellTypes_colors, ConnectGroups=TRUE, ncol = 6, nrow = 2, widthTitle=20, y_limits = NULL, legend_nrow = 2,cond_cohend=cond_cohend)

df_logmedian <- CalculateScores(data = corrcounts_merge, metadata = metadata_merge, method = "logmedian", gene_sets = SenescenceSignatures)

senescence_triggers_colors <- c(
  "none" = "#E57373",  # Soft red  
  "Radiation" = "#BDBDBD",  # Medium gray  
  "DNA damage" = "#64B5F6",  # Brighter blue  
  "Telomere shortening" = "#4FC3F7",  # Vivid sky blue  
  "DNA demethylation" = "#BA68C8",  # Rich lavender  
  "Oxidative stress" = "#FDD835",  # Strong yellow  
  "Conditioned Medium" = "#F2994A",  # Warm orange  
  "Oncogene" = "#81C784",  # Medium green  
  "Lipid Accumulation" = "#E57373",  # Coral  
  "Calcium influx" = "#26A69A",  # Deep teal  
  "Plasma membrane dysruption" = "#D32F2F",  # Strong salmon  
  "OSKM factors" = "#FFB74D",  # Bright peach  
  "YAP KO" = "#9575CD"  # Deep pastel purple  
)

cellTypes_colors <- c(
  "Fibroblast" = "#FF6961",   # Strong Pastel Red  
  "Keratinocyte" = "#FFB347", # Strong Pastel Orange  
  "Melanocyte" = "#FFD700",   # Strong Pastel Yellow  
  "Endothelial" = "#77DD77",  # Strong Pastel Green  
  "Neuronal" = "#779ECB",     # Strong Pastel Blue  
  "Mesenchymal" = "#C27BA0"   # Strong Pastel Purple  
)

cond_cohend <- list(A=c("Senescent"), # if no variable is defined, will be the first that appears in the ggplot
                    B=c("Proliferative","Quiescent"))

PlotScores(ResultsList = df_logmedian, ColorVariable = "CellType", GroupingVariable="Condition",  method ="logmedian", ColorValues = cellTypes_colors, ConnectGroups=TRUE, ncol = 6, nrow = 2, widthTitle=20, y_limits = NULL, legend_nrow = 2,xlab=NULL, cond_cohend = cond_cohend)

df_ranking <- CalculateScores(data = corrcounts_merge, metadata = metadata_merge, method = "ranking", gene_sets = SenescenceSignatures)
Considering unidirectional gene signature mode for signature [DOWN]_CellAge
Considering unidirectional gene signature mode for signature [DOWN]_HernandezSegura
Considering unidirectional gene signature mode for signature [DOWN]_SeneQuest
Considering unidirectional gene signature mode for signature [UP]_CellAge
Considering unidirectional gene signature mode for signature [UP]_HernandezSegura
Considering unidirectional gene signature mode for signature [UP]_SeneQuest
Considering unidirectional gene signature mode for signature CSgene
Considering unidirectional gene signature mode for signature GOBP_CELLULAR_SENESCENCE
Considering unidirectional gene signature mode for signature GOBP_NEGATIVE_REGULATION_OF_CELLULAR_SENESCENCE
Considering unidirectional gene signature mode for signature GOBP_POSITIVE_REGULATION_OF_CELLULAR_SENESCENCE
Considering unidirectional gene signature mode for signature REACTOME_CELLULAR_SENESCENCE
Considering unidirectional gene signature mode for signature SAUL_SEN_MAYO
senescence_triggers_colors <- c(
  "none" = "#E57373",  # Soft red  
  "Radiation" = "#BDBDBD",  # Medium gray  
  "DNA damage" = "#64B5F6",  # Brighter blue  
  "Telomere shortening" = "#4FC3F7",  # Vivid sky blue  
  "DNA demethylation" = "#BA68C8",  # Rich lavender  
  "Oxidative stress" = "#FDD835",  # Strong yellow  
  "Conditioned Medium" = "#F2994A",  # Warm orange  
  "Oncogene" = "#81C784",  # Medium green  
  "Lipid Accumulation" = "#E57373",  # Coral  
  "Calcium influx" = "#26A69A",  # Deep teal  
  "Plasma membrane dysruption" = "#D32F2F",  # Strong salmon  
  "OSKM factors" = "#FFB74D",  # Bright peach  
  "YAP KO" = "#9575CD"  # Deep pastel purple  
)

cellTypes_colors <- c(
  "Fibroblast" = "#FF6961",   # Strong Pastel Red  
  "Keratinocyte" = "#FFB347", # Strong Pastel Orange  
  "Melanocyte" = "#FFD700",   # Strong Pastel Yellow  
  "Endothelial" = "#77DD77",  # Strong Pastel Green  
  "Neuronal" = "#779ECB",     # Strong Pastel Blue  
  "Mesenchymal" = "#C27BA0"   # Strong Pastel Purple  
)

cond_cohend <- list(A=c("Senescent"), # if no variable is defined, will be the first that appears in the ggplot
                    B=c("Proliferative","Quiescent"))

PlotScores(ResultsList = df_ranking, ColorVariable = "CellType", GroupingVariable="Condition",  method ="ranking", ColorValues = cellTypes_colors, ConnectGroups=TRUE, ncol = 6, nrow = 2, widthTitle=20, y_limits = NULL, legend_nrow = 2,xlab=NULL, cond_cohend = cond_cohend)

wrap_title_aux <- function(title, width = 30) {
  if (nchar(title) <= width) {
    return(title)  # No need to wrap if it fits
  }
  
  wrapped_title <- ""
  while (nchar(title) > width) {
    # Find positions of capital letters and symbols near the wrap point
    capital_pos <- gregexpr("[A-Z]", title)[[1]]
    symbol_pos <- gregexpr("(_|-|:)", title)[[1]]
    
    # Check for symbol breaks within the last few characters (width - 5 to width)
    valid_symbol_breaks <- symbol_pos[symbol_pos >= (width - 5) & symbol_pos <= width]
    
    if (length(valid_symbol_breaks) > 0) {
      # If a suitable symbol is found, break at the first valid symbol
      break_at <- valid_symbol_breaks[1]
    } else {
      # If no suitable symbol, look for capital letters within the same range
      valid_capital_breaks <- capital_pos[capital_pos >= (width - 5) & capital_pos <= width]
      
      if (length(valid_capital_breaks) > 0) {
        # If a capital letter is found, break just before the capital letter
        break_at <- valid_capital_breaks[1] - 1
      } else {
        # If no suitable symbol or capital letter, break at width
        break_at <- width
      }
    }
    
    # Append the wrapped line
    wrapped_title <- paste0(wrapped_title, substr(title, 1, break_at), "\n")
    
    # Update title with the remaining text after the break
    title <- substr(title, break_at + 1, nchar(title))
  }
  
  # Add the remaining part of the title
  wrapped_title <- paste0(wrapped_title, title)
  
  return(wrapped_title)
}

plotlist <- list()

for (sig in names(df_ssGSEA)){
  
  df_subset_ssGSEA <- df_ssGSEA[[sig]]
  df_subset_logmedian <- df_logmedian[[sig]]
  
  df_subset_merge <- merge(df_subset_ssGSEA,df_subset_logmedian,by="sample")
  
  # Wrap the signature name using the helper function
  wrapped_title <- wrap_title_aux(sig, width = 20)  
  
  plotlist[[sig]] <- ggplot2::ggplot(df_subset_merge, aes(x=score.x, y=score.y)) +
    geom_point(size=4, alpha=0.8, fill="darkgrey", shape=21) +
    theme_bw() +
    xlab("ssGSEA Enrichment Score") +
    ylab("Normalised Signature Score") +
    ggtitle(wrapped_title) +
    theme(plot.title = ggplot2::element_text(hjust = 0.5, size=10),
          plot.subtitle = ggplot2::element_text(hjust = 0.5)) 
  
}
Error in names(df_ssGSEA) : object 'df_ssGSEA' not found

Bidirectional gene signatures

Try scores with bidirectional signatures

bidirectsigs <- readRDS("~/VersionControl/senescence_benchmarking/CommonFiles/SenescenceSignatures_complete_newCellAge.RDS")
for (sig in names(bidirectsigs)){
  sigdf <- bidirectsigs[[sig]]
  sigdf <- sigdf[,1:2] # remove the third column, if applicable
  if(any(sigdf[,2]=="not_reported")){
    sigdf <- sigdf[,1]
    bidirectsigs[[sig]] <- sigdf
    next 
  }
  sigdf[,2] <- ifelse(sigdf[,2]=="enriched",1,-1)
  bidirectsigs[[sig]] <- sigdf
}
bidirectsigs
$CellAge

$CSgene
  [1] "TP53"       "TERF2"      "MAPK14"     "CDKN2A"     "CDKN1A"     "CCNE1"      "CCNA1"      "MAPKAPK5"   "CBX4"       "TXN"       
 [11] "TBX2"       "STAT3"      "SRF"        "BMI1"       "MAP2K4"     "MAP2K6"     "MAP2K3"     "MAPK8"      "MAPK3"      "MAPK1"     
 [21] "PRKCD"      "PML"        "OPA1"       "ATM"        "MDM2"       "CXCL8"      "IL6"        "IGFBP7"     "ID1"        "HRAS"      
 [31] "H2AFX"      "POT1"       "SIRT1"      "KDM6B"      "PLA2R1"     "EZH2"       "E2F3"       "E2F1"       "CEBPB"      "CDKN2D"    
 [41] "CDKN2B"     "CDKN1B"     "CDK6"       "CDK4"       "CDK2"       "CDC42"      "RBX1"       "CDC27"      "CDK1"       "MAML1"     
 [51] "CD44"       "MAD2L1BP"   "MAP4K4"     "AIM2"       "RECQL4"     "ARHGAP18"   "KL"         "MAPKAPK2"   "AURKB"      "SLC16A7"   
 [61] "CCNE2"      "HIST1H2BJ"  "HIST1H3F"   "CCNA2"      "MCM3AP"     "CDC16"      "TSC22D1"    "CBS"        "TNFSF13"    "CTNNAL1"   
 [71] "EED"        "PNPT1"      "CDC23"      "RNASET2"    "TP63"       "CAV1"       "MKNK1"      "TSLP"       "HIST1H2BK"  "PPM1D"     
 [81] "HAVCR2"     "CBX2"       "KDM2B"      "DPY30"      "C2orf40"    "YPEL3"      "HIST2H4A"   "HIST1H4L"   "HIST1H4E"   "HIST1H4B"  
 [91] "HIST1H4H"   "HIST1H4C"   "HIST1H4J"   "HIST1H4K"   "HIST1H4F"   "HIST1H4D"   "HIST1H4A"   "HIST1H3B"   "HIST1H3H"   "HIST1H3J"  
[101] "HIST1H3G"   "HIST1H3I"   "HIST1H3E"   "HIST1H3C"   "HIST1H3D"   "HIST1H3A"   "HIST2H2BE"  "HIST1H2BO"  "HIST1H2BC"  "HIST1H2BI" 
[111] "HIST1H2BH"  "HIST1H2BE"  "HIST1H2BF"  "HIST1H2BM"  "HIST1H2BN"  "HIST1H2BL"  "HIST1H2BG"  "HIST2H2AC"  "HIST2H2AA3" "HIST1H2AB" 
[121] "HIST1H2AC"  "HIST1H2AJ"  "HIST1H4I"   "HIST3H3"    "CALR"       "HMGA2"      "PHC3"       "KAT6A"      "EHMT1"      "SMC6"      
[131] "AIMP2"      "CALCA"      "DEK"        "MAPKAPK3"   "ZNF148"     "YY1"        "WRN"        "WNT5A"      "NR1H2"      "UBE3A"     
[141] "UBE2E1"     "UBE2D1"     "UBC"        "UBB"        "UBA52"      "CDC26P1"    "TYMS"       "TWIST1"     "HIRA"       "RPS27AP11" 
[151] "HIST2H2AA4" "TP73"       "TOP 1,00"   "TNF"        "TGFB2"      "TGFB1"      "TFDP1"      "TERT"       "TERF1"      "BUB1B"     
[161] "BUB1"       "TCF3"       "TBX3"       "TAGLN"      "STAT6"      "STAT1"      "BRAF"       "SREBF1"     "BRCA1"      "SP1"       
[171] "SOX5"       "SOD2"       "SNAI1"      "SMARCB1"    "SMARCA2"    "HIST2H3D"   "PHC1P1"     "ACD"        "SKIL"       "LOC649620" 
[181] "SLC13A3"    "LOC647654"  "SMURF2"     "ANAPC1"     "SHC1"       "CPEB1"      "H3F3AP6"    "ZMAT3"      "RBBP4P1"    "SRSF3"     
[191] "SRSF1"      "SATB1"      "S100A6"     "RXRB"       "RRM2"       "RRM1"       "RPS27A"     "RPS6KA3"    "RPS6KA2"    "RPS6KA1"   
[201] "RPL5"       "RNF2"       "RIT1"       "RING1"      "BCL2L1"     "RELA"       "BCL2"       "CCND1"      "RBP2"       "RBL2"      
[211] "RBL1"       "RBBP7"      "RBBP4"      "NTN4"       "RB1"        "IL21"       "RAN"        "RAF1"       "RAC1"       "TNRC6C"    
[221] "KIAA1524"   "EP400"      "CNOT6"      "CBX8"       "PTEN"       "SEPN1"      "BACH1"      "PSMB5"      "PROX1"      "PRL"       
[231] "MAP2K7"     "MAP2K1"     "MAPK10"     "MAPK9"      "MAPK11"     "MAPK7"      "PRKDC"      "RNF114"     "PRKCI"      "ATF7IP"    
[241] "MFN1"       "PRKAA2"     "CDKN2AIP"   "RBM38"      "PRG2"       "HIST2H4B"   "HJURP"      "TMEM140"    "PBRM1"      "Mar-05"    
[251] "PPARG"      "PPARD"      "POU2F1"     "TERF2IP"    "ERRFI1"     "H2BFS"      "PLK1"       "PLAUR"      "PIN1"       "PIM1"      
[261] "PIK3CA"     "PHB"        "PGR"        "PGD"        "PIAS4"      "PDGFB"      "SIRT6"      "ANAPC11"    "ANAPC7"     "ANAPC5"    
[271] "WNT16"      "FZR1"       "ZBTB7A"     "ERGIC2"     "PCNA"       "FIS1"       "PAX3"       "NOX4"       "MINK1"      "PEBP1"     
[281] "YBX1"       "NINJ1"      "NFKB1"      "H2AFB1"     "NDN"        "NCAM1"      "NBN"        "MYC"        "MYBL2"      "MSN"       
[291] "ASS1"       "LOC441488"  "MRE11A"     "MOV10"      "MMP7"       "MIF"        "MAP3K5"     "MAP3K1"     "MECP2"      "MCL1"      
[301] "MAGEA2"     "SMAD9"      "SMAD7"      "SMAD6"      "SMAD5"      "SMAD4"      "SMAD3"      "SMAD2"      "SMAD1"      "MAD2L1"    
[311] "MXD1"       "MIR34A"     "MIR30A"     "MIR299"     "MIR29A"     "MIR22"      "MIR217"     "MIR21"      "MIR205"     "MIR203A"   
[321] "MIR191"     "MIR146A"    "MIR141"     "MIR10B"     "ARNTL"      "LMNB1"      "LMNA"       "LGALS9"     "RHOA"       "KRT5"      
[331] "KRAS"       "KIT"        "KIR2DL4"    "KCNJ12"     "JUN"        "JAK2"       "ITGB4"      "IRS1"       "IRF7"       "IRF5"      
[341] "IRF3"       "ING1"       "IDO1"       "ILF3"       "IL15"       "IL12B"      "CXCR2"      "IL4"        "IGFBP5"     "IGFBP3"    
[351] "IGFBP1"     "IGF1R"      "IGF1"       "H3F3AP5"    "IFNG"       "IFI16"      "IDH1"       "ID2"        "HIST2H3A"   "BIRC5"     
[361] "HSPB1"      "HSPA9"      "HSPA5"      "HSPA1A"     "APEX1"      "HNRNPA1"    "FOXA3"      "FOXA2"      "FOXA1"      "HMGA1"     
[371] "HIF1A"      "ANXA5"      "HELLS"      "HDAC1"      "H3F3B"      "H3F3A"      "HIST1H2BB"  "HIST1H2BD"  "H2AFZ"      "HIST1H2AD" 
[381] "HIST1H2AE"  "ANAPC4"     "ANAPC2"     "UBN1"       "SENP1"      "GUCY2C"     "GSK3B"      "UHRF1"      "BRD7"       "NSMCE2"    
[391] "PTRF"       "GPI"        "GNAO1"      "RPS6KA6"    "TNRC6A"     "AGO2"       "B3GAT1"     "DNAJC2"     "GJA1"       "AGO1"      
[401] "EHF"        "TINF2"      "LDLRAP1"    "ULK3"       "GAPDH"      "ABI3BP"     "ASF1A"      "HIST1H2BA"  "G6PD"       "ACKR1"     
[411] "MTOR"       "CDC26"      "CNOT6L"     "FOS"        "CABIN1"     "MORC3"      "SUZ12"      "NPTXR"      "CBX6"       "SIRT3"     
[421] "CRTC1"      "PPP1R13B"   "SUN1"       "SMC5"       "TNRC6B"     "FOXO1"      "FOXM1"      "TNIK"       "SCMH1"      "DKK 1,00"  
[431] "FGFR2"      "FGF2"       "HEPACAM"    "FANCD2"     "EWSR1"      "ETS2"       "ETS1"       "ESR2"       "ERF"        "AKT1"      
[441] "EREG"       "ERBB2"      "ENG"        "ELN"        "CRTC2"      "EIF5A"      "EGR1"       "EGFR"       "EEF1B2"     "AGO4"      
[451] "AGO3"       "EEF1A1"     "PHC2"       "PHC1"       "ABCA1"      "E2F2"       "DUSP6"      "DUSP4"      "HBEGF"      "AGT"       
[461] "DNMT3A"     "AGER"       "DKC1"       "DAXX"       "CYP3A4"     "CTSZ"       "CTSD"       "CSNK2A1"    "E2F7"       "PARP1"     
[471] "HIST3H2BB"  "HIST2H3C"   "JDP2"       "HIST4H4"    "CLU"        "CKB"        "RASSF1"     "CHEK1"      "TOPBP1"     "UBE2C"     
[481] "KIF2C"      "BTG3"       "EHMT2"      "GADD45G"    "NEK6"       "ZMYND11"    "SPINT2"     "CENPA"      "AGR2"       "CEBPG"     
[491] "HYOU1"      "TADA3"      "MCRS1"      "NDRG1"      "ANAPC10"    "CDKN2C"     "ZMPSTE24"   "PSMD14"     "NAMPT"      "RAD50"     
[501] "TRIM10"     "DNM1L"      "BCL2L11"   

$GOBP_CELLULAR_SENESCENCE
  [1] "AKT3"     "MIR543"   "CDK2"     "CDK6"     "CDKN1A"   "ZMPSTE24" "CDKN1B"   "CDKN2A"   "CDKN2B"   "CITED2"   "KAT5"     "PLK2"    
 [13] "NEK6"     "ZNF277"   "CGAS"     "COMP"     "MAPK14"   "VASH1"    "PLA2R1"   "SMC5"     "SIRT1"    "MORC3"    "NUP62"    "ABL1"    
 [25] "ULK3"     "RSL1D1"   "FBXO5"    "FBXO4"    "MAGEA2B"  "NSMCE2"   "H2AX"     "HLA-G"    "HMGA1"    "HRAS"     "ID2"      "IGF1R"   
 [37] "ING2"     "KIR2DL4"  "ARG2"     "LMNA"     "BMAL1"    "MIR10A"   "MIR146A"  "MIR17"    "MIR188"   "MIR217"   "MIR22"    "MIR34A"  
 [49] "MAGEA2"   "MAP3K3"   "MAP3K5"   "MIF"      "MNT"      "ATM"      "NPM1"     "YBX1"     "OPA1"     "PAWR"     "ABI3"     "FZR1"    
 [61] "WNT16"    "SIRT6"    "PML"      "PRMT6"    "PRELP"    "PRKCD"    "MAPK8"    "MAPK11"   "MAPK9"    "MAPK10"   "MAP2K1"   "MAP2K3"  
 [73] "MAP2K6"   "MAP2K7"   "B2M"      "ZMIZ1"    "PTEN"     "MIR20B"   "RBL1"     "BCL6"     "MAP2K4"   "BMPR1A"   "SPI1"     "SRF"     
 [85] "BRCA2"    "NEK4"     "TBX2"     "TBX3"     "MIR590"   "TERC"     "TERF2"    "TERT"     "TOP2B"    "TP53"     "TWIST1"   "WNT1"    
 [97] "WRN"      "SMC6"     "KAT6A"    "ZKSCAN3"  "HMGA2"    "CALR"     "YPEL3"    "ECRG4"    "MAPKAPK5" "TP63"     "PNPT1"    "DNAJA3"  
[109] "EEF1E1"   "NUAK1"   

$GOBP_NEGATIVE_REGULATION_OF_CELLULAR_SENESCENCE

$GOBP_POSITIVE_REGULATION_OF_CELLULAR_SENESCENCE

$HernandezSegura

$REACTOME_CELLULAR_SENESCENCE
  [1] "CDC27"    "E2F2"     "SCMH1"    "MRE11"    "MAP2K3"   "MAPK9"    "ANAPC4"   "MAP2K4"   "MAP4K4"   "RPS6KA2"  "UBE2D1"   "EED"     
 [13] "MAP2K7"   "TNRC6C"   "MAPKAPK5" "ANAPC5"   "TNRC6A"   "TINF2"    "AGO1"     "CDC23"    "CABIN1"   "MAPK1"    "HIRA"     "TNRC6B"  
 [25] "E2F1"     "RBBP7"    "MAPK3"    "ACD"      "NBN"      "CCNE1"    "FZR1"     "ERF"      "CDK6"     "H2AZ2"    "EZH2"     "MAPK8"   
 [37] "UBE2S"    "MAP2K6"   "NFKB1"    "MAPK10"   "ANAPC15"  "CDKN1B"   "PHC1"     "ASF1A"    "MAPK14"   "E2F3"     "LMNB1"    "RAD50"   
 [49] "TFDP2"    "MAPKAPK3" "IL1A"     "RPS6KA1"  "UBN1"     "RNF2"     "CDKN2C"   "CDK2"     "H1-3"     "H1-1"     "H2BC11"   "CDKN1A"  
 [61] "ID1"      "AGO3"     "POT1"     "CDKN2D"   "CDC16"    "H3-3B"    "KDM6B"    "TERF2"    "CCNA1"    "PHC2"     "AGO4"     "ETS1"    
 [73] "CDK4"     "MDM2"     "IL6"      "TXN"      "HMGA1"    "RB1"      "MINK1"    "TP53"     "ANAPC11"  "CBX8"     "CBX4"     "RPS27A"  
 [85] "CCNA2"    "H2BC1"    "TERF1"    "CDKN2B"   "CDKN2A"   "ATM"      "HMGA2"    "UBC"      "VENTX"    "ANAPC1"   "TNIK"     "MOV10"   
 [97] "ETS2"     "H2BC5"    "H4C8"     "RBBP4"    "MAPKAPK2" "H3-3A"    "IGFBP7"   "ANAPC10"  "ANAPC16"  "MAPK7"    "TERF2IP"  "H3-4"    
[109] "BMI1"     "H1-4"     "STAT3"    "CXCL8"    "UBE2E1"   "UBB"      "FOS"      "IFNB1"    "CEBPB"    "KAT5"     "RELA"     "PHC3"    
[121] "CBX2"     "UBE2C"    "CCNE2"    "ANAPC2"   "CDC26"    "RPS6KA3"  "JUN"      "SUZ12"    "H2AC6"    "H2BC4"    "EHMT1"    "EP400"   
[133] "H3C13"    "CBX6"     "H2AC20"   "H1-5"     "H2BC21"   "H2BC13"   "MAPK11"   "SP1"      "H1-2"     "H2AX"     "H1-0"     "ANAPC7"  
[145] "H2AC7"    "H2BC26"   "H4C3"     "H3C12"    "H4C11"    "H3C4"     "MAP3K5"   "H4C16"    "H2BC12"   "TFDP1"    "MDM4"     "H3C14"   
[157] "H3C15"    "RING1"    "EHMT2"    "UBA52"    "H2AJ"     "H4C15"    "H4C14"    "H4C12"    "H2BC14"   "H2BC8"    "H3C8"     "H2AB1"   
[169] "H2BC6"    "H4C6"     "H2BC17"   "H3C6"     "H4C13"    "H3C11"    "H2BC9"    "H3C1"     "H4C9"     "H2AC14"   "H2BC3"    "H4C5"    
[181] "H2AC8"    "H4C4"     "H2BC7"    "H3C7"     "H2AC4"    "H2BC10"   "H4C1"     "H4C2"     "H3C10"    "MIR24-2"  "MIR24-1"  "H3C2"    
[193] "H3C3"     "H2AC18"   "H2AC19"  

$SAUL_SEN_MAYO

$SeneQuest
NA
df_logmedian <- CalculateScores(data = corrcounts_merge, metadata = metadata_merge, method = "logmedian", gene_sets = bidirectsigs)
Considering bidirectional gene signature mode for signature CellAge
Considering unidirectional gene signature mode for signature CSgene
Considering unidirectional gene signature mode for signature GOBP_CELLULAR_SENESCENCE
Considering unidirectional gene signature mode for signature GOBP_NEGATIVE_REGULATION_OF_CELLULAR_SENESCENCE
Considering unidirectional gene signature mode for signature GOBP_POSITIVE_REGULATION_OF_CELLULAR_SENESCENCE
Considering bidirectional gene signature mode for signature HernandezSegura
Considering unidirectional gene signature mode for signature REACTOME_CELLULAR_SENESCENCE
Considering unidirectional gene signature mode for signature SAUL_SEN_MAYO
Considering bidirectional gene signature mode for signature SeneQuest
senescence_triggers_colors <- c(
  "none" = "#E57373",  # Soft red  
  "Radiation" = "#BDBDBD",  # Medium gray  
  "DNA damage" = "#64B5F6",  # Brighter blue  
  "Telomere shortening" = "#4FC3F7",  # Vivid sky blue  
  "DNA demethylation" = "#BA68C8",  # Rich lavender  
  "Oxidative stress" = "#FDD835",  # Strong yellow  
  "Conditioned Medium" = "#F2994A",  # Warm orange  
  "Oncogene" = "#81C784",  # Medium green  
  "Lipid Accumulation" = "#E57373",  # Coral  
  "Calcium influx" = "#26A69A",  # Deep teal  
  "Plasma membrane dysruption" = "#D32F2F",  # Strong salmon  
  "OSKM factors" = "#FFB74D",  # Bright peach  
  "YAP KO" = "#9575CD"  # Deep pastel purple  
)

cellTypes_colors <- c(
  "Fibroblast" = "#FF6961",   # Strong Pastel Red  
  "Keratinocyte" = "#FFB347", # Strong Pastel Orange  
  "Melanocyte" = "#FFD700",   # Strong Pastel Yellow  
  "Endothelial" = "#77DD77",  # Strong Pastel Green  
  "Neuronal" = "#779ECB",     # Strong Pastel Blue  
  "Mesenchymal" = "#C27BA0"   # Strong Pastel Purple  
)

cond_cohend <- list(A=c("Senescent"), # if no variable is defined, will be the first that appears in the ggplot
                    B=c("Proliferative","Quiescent"))

PlotScores(ResultsList = df_logmedian, ColorVariable = "CellType", GroupingVariable="Condition",  method ="logmedian", ColorValues = cellTypes_colors, ConnectGroups=TRUE, ncol = 3, nrow = 3, widthTitle=20, y_limits = NULL, legend_nrow = 2,xlab=NULL, cond_cohend = cond_cohend)

 
df_ssgsea <- CalculateScores(data = corrcounts_merge, metadata = metadata_merge, method = "ssGSEA", gene_sets = bidirectsigs)
Considering bidirectional gene signature mode for signature CellAge
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering unidirectional gene signature mode for signature CSgene
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering unidirectional gene signature mode for signature GOBP_CELLULAR_SENESCENCE
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering unidirectional gene signature mode for signature GOBP_NEGATIVE_REGULATION_OF_CELLULAR_SENESCENCE
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering unidirectional gene signature mode for signature GOBP_POSITIVE_REGULATION_OF_CELLULAR_SENESCENCE
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering bidirectional gene signature mode for signature HernandezSegura
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering unidirectional gene signature mode for signature REACTOME_CELLULAR_SENESCENCE
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering unidirectional gene signature mode for signature SAUL_SEN_MAYO
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Considering bidirectional gene signature mode for signature SeneQuest
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = FALSE.
No id variables; using all as measure variables
senescence_triggers_colors <- c(
  "none" = "#E57373",  # Soft red  
  "Radiation" = "#BDBDBD",  # Medium gray  
  "DNA damage" = "#64B5F6",  # Brighter blue  
  "Telomere shortening" = "#4FC3F7",  # Vivid sky blue  
  "DNA demethylation" = "#BA68C8",  # Rich lavender  
  "Oxidative stress" = "#FDD835",  # Strong yellow  
  "Conditioned Medium" = "#F2994A",  # Warm orange  
  "Oncogene" = "#81C784",  # Medium green  
  "Lipid Accumulation" = "#E57373",  # Coral  
  "Calcium influx" = "#26A69A",  # Deep teal  
  "Plasma membrane dysruption" = "#D32F2F",  # Strong salmon  
  "OSKM factors" = "#FFB74D",  # Bright peach  
  "YAP KO" = "#9575CD"  # Deep pastel purple  
)

cellTypes_colors <- c(
  "Fibroblast" = "#FF6961",   # Strong Pastel Red  
  "Keratinocyte" = "#FFB347", # Strong Pastel Orange  
  "Melanocyte" = "#FFD700",   # Strong Pastel Yellow  
  "Endothelial" = "#77DD77",  # Strong Pastel Green  
  "Neuronal" = "#779ECB",     # Strong Pastel Blue  
  "Mesenchymal" = "#C27BA0"   # Strong Pastel Purple  
)

cond_cohend <- list(A=c("Senescent"), # if no variable is defined, will be the first that appears in the ggplot
                    B=c("Proliferative","Quiescent"))

PlotScores(ResultsList = df_ssgsea, ColorVariable = "CellType", GroupingVariable="Condition",  method ="ssGSEA", ColorValues = cellTypes_colors, ConnectGroups=TRUE, ncol = 3, nrow = 3, widthTitle=20, y_limits = NULL, legend_nrow = 2,xlab=NULL, cond_cohend = cond_cohend)

 
df_ranking <- CalculateScores(data = corrcounts_merge, metadata = metadata_merge, method = "ranking", gene_sets = bidirectsigs)
Considering bidirectional gene signature mode for signature CellAge
Considering unidirectional gene signature mode for signature CSgene
Considering unidirectional gene signature mode for signature GOBP_CELLULAR_SENESCENCE
Considering unidirectional gene signature mode for signature GOBP_NEGATIVE_REGULATION_OF_CELLULAR_SENESCENCE
Considering unidirectional gene signature mode for signature GOBP_POSITIVE_REGULATION_OF_CELLULAR_SENESCENCE
Considering bidirectional gene signature mode for signature HernandezSegura
Considering unidirectional gene signature mode for signature REACTOME_CELLULAR_SENESCENCE
Considering unidirectional gene signature mode for signature SAUL_SEN_MAYO
Considering bidirectional gene signature mode for signature SeneQuest
senescence_triggers_colors <- c(
  "none" = "#E57373",  # Soft red  
  "Radiation" = "#BDBDBD",  # Medium gray  
  "DNA damage" = "#64B5F6",  # Brighter blue  
  "Telomere shortening" = "#4FC3F7",  # Vivid sky blue  
  "DNA demethylation" = "#BA68C8",  # Rich lavender  
  "Oxidative stress" = "#FDD835",  # Strong yellow  
  "Conditioned Medium" = "#F2994A",  # Warm orange  
  "Oncogene" = "#81C784",  # Medium green  
  "Lipid Accumulation" = "#E57373",  # Coral  
  "Calcium influx" = "#26A69A",  # Deep teal  
  "Plasma membrane dysruption" = "#D32F2F",  # Strong salmon  
  "OSKM factors" = "#FFB74D",  # Bright peach  
  "YAP KO" = "#9575CD"  # Deep pastel purple  
)

cellTypes_colors <- c(
  "Fibroblast" = "#FF6961",   # Strong Pastel Red  
  "Keratinocyte" = "#FFB347", # Strong Pastel Orange  
  "Melanocyte" = "#FFD700",   # Strong Pastel Yellow  
  "Endothelial" = "#77DD77",  # Strong Pastel Green  
  "Neuronal" = "#779ECB",     # Strong Pastel Blue  
  "Mesenchymal" = "#C27BA0"   # Strong Pastel Purple  
)

cond_cohend <- list(A=c("Senescent"), # if no variable is defined, will be the first that appears in the ggplot
                    B=c("Proliferative","Quiescent"))

PlotScores(ResultsList = df_ranking, ColorVariable = "CellType", GroupingVariable="Condition",  method ="ranking", ColorValues = cellTypes_colors, ConnectGroups=TRUE, ncol = 3, nrow = 3, widthTitle=20, y_limits = NULL, legend_nrow = 2,xlab=NULL, cond_cohend = cond_cohend)

Ranking

data <- corrcounts_merge
metadata <- metadata_merge


# if unidirectional but vector



# if unidirectional but data frame

# if bidirectional

getRanking <- function(data,sample, geneset){
  
  expressiongene <- data[,sample] # isolate one sample and get the expression of all genes
  names(expressiongene) <- row.names(data) # name vector
  expressiongene <- expressiongene[order(expressiongene, decreasing = F)] # order vector from least expressed to most expressed
  ranking <- match(geneset,names(expressiongene)) # see where genes in the geneset appear in the ordered list of genes 
  ranking <- as.vector(na.omit(ranking)) # omit genes that were not found
  
  return(sum(ranking)) # return also number of genes found
  
}


CalculateScores_Ranking <- function(data, metadata = NULL, gene_sets) {
  
  ResultsList <- list()
  
  # define universe of genes
  universe_genes <- row.names(data)
  ranking_genes <- c(1:length(universe_genes))
  
  for (sig in names(gene_sets)) {
    
    signature <- gene_sets[[sig]]
    
    if (is.data.frame(signature)) {  # If a data frame, check enrichment direction
      
      # number of values in the enrichment column
      nb_factors <- length(unique(signature[, 2]))
      
      if (nb_factors > 1) {  # Both up (1) and down (-1) genes
        
        message(paste0("Considering bidirectional gene signature mode for signature ", sig))
        
        totalnb_genes <- sum(signature[1,] %in% row.names(data))
        
        signaturegenes_up <- signature[signature[,2]==1,]
        signaturegenes_down <- signature[signature[,2]==-1,]
        
        # Apply getRanking function to each sample (column)
        rankings_up <-   sapply(colnames(data), function(sample) getRanking(data, sample, signaturegenes_up))
        rankings_dowm <-   sapply(colnames(data), function(sample) getRanking(data, sample, signaturegenes_down))
        
        ranking_final <- (rankings_up-rankings_dowm)/totalnb_genes
        ranking_final <- data.frame(sample=colnames(data),
                                    score=ranking_final)
        
        
        
        
      } else {  # Only one known direction (up or down)
        
        message(paste0("Considering unidirectional gene signature mode for signature ", sig))
        
        signaturegenes <- signature[,1]
        totalnb_genes <- sum(signaturegenes %in% row.names(data))
        
        # Apply getRanking function to each sample (column) 
        rankings <-   sapply(colnames(data), function(sample) getRanking(data, sample, signaturegenes))
        
        ranking_final <- rankings/totalnb_genes
        ranking_final <- data.frame(sample=colnames(data),
                                    score=ranking_final)
        
        
      }
    } else {  # If vector of genes (unidirectional)
      message(paste0("Considering unidirectional gene signature mode for signature ", sig))
      
      
      signaturegenes <- signature 
      totalnb_genes <- sum(signaturegenes %in% row.names(data))
      
      # Apply getRanking function to each sample (column) 
      rankings <-   sapply(colnames(data), function(sample) getRanking(data, sample, signaturegenes))
      
      ranking_final <- rankings/totalnb_genes
      ranking_final <- data.frame(sample=colnames(data),
                                  score=ranking_final)
      
    }
    
    if (!is.null(metadata)) ranking_final <- merge(ranking_final, metadata, by = "sample")
    
    row.names(ranking_final) <- NULL
    
    ResultsList[[sig]] <- ranking_final
    
  }
  
  return(ResultsList)
}
  • include in plotScores the new option for ranking
  • include in calculateScores the new option for ranking
PlotScores(ResultsList = df_ranking, ColorVariable = "CellType", GroupingVariable="Condition",  method ="logmedian", ColorValues = cellTypes_colors, ConnectGroups=TRUE, ncol = 3, nrow = 3, widthTitle=20, y_limits = NULL, legend_nrow = 2,xlab=NULL, cond_cohend = cond_cohend)
Warning in PlotScores(ResultsList = df_ranking, ColorVariable = "CellType",  :
  Warning: Not all conditions of GroupingVariable  were specified for Cohen's d calculation
Warning in PlotScores(ResultsList = df_ranking, ColorVariable = "CellType",  :
  Warning: Not all conditions of GroupingVariable  were specified for Cohen's d calculation
Warning in PlotScores(ResultsList = df_ranking, ColorVariable = "CellType",  :
  Warning: Not all conditions of GroupingVariable  were specified for Cohen's d calculation
Warning in PlotScores(ResultsList = df_ranking, ColorVariable = "CellType",  :
  Warning: Not all conditions of GroupingVariable  were specified for Cohen's d calculation
Warning in PlotScores(ResultsList = df_ranking, ColorVariable = "CellType",  :
  Warning: Not all conditions of GroupingVariable  were specified for Cohen's d calculation
Warning in PlotScores(ResultsList = df_ranking, ColorVariable = "CellType",  :
  Warning: Not all conditions of GroupingVariable  were specified for Cohen's d calculation
Warning in PlotScores(ResultsList = df_ranking, ColorVariable = "CellType",  :
  Warning: Not all conditions of GroupingVariable  were specified for Cohen's d calculation
Warning in PlotScores(ResultsList = df_ranking, ColorVariable = "CellType",  :
  Warning: Not all conditions of GroupingVariable  were specified for Cohen's d calculation
Warning in PlotScores(ResultsList = df_ranking, ColorVariable = "CellType",  :
  Warning: Not all conditions of GroupingVariable  were specified for Cohen's d calculation

Individual Genes

Violin Expression Plots

IndividualGenes_Violins(data = corrcounts_merge, metadata = metadata_merge, genes = c("CDKN1A", "CDKN2A", "GLB1","TP53","CCL2"), GroupingVariable = "Condition", plot=T, ncol=NULL, nrow=2, divide="CellType", invert_divide=FALSE,ColorValues=senescence_triggers_colors, pointSize=2, ColorVariable="SenescentType", title="Senescence", widthTitle=16,y_limits = NULL,legend_nrow=4, xlab="Condition",colorlab="") 
Using gene as id variables

Correlation Heatmap

CorrelationHeatmap(data=corrcounts_merge, 
                   metadata = metadata_merge, 
                   genes=c("CDKN1A", "CDKN2A", "GLB1","TP53","CCL2"), 
                   separate.by = "Condition", 
                   method = "pearson",  
                   colorlist = list(low = "#3F4193", mid = "#F9F4AE", high = "#B44141"),
                   limits_colorscale = c(-1,0,1), 
                   widthTitle = 16, 
                   title = "test", 
                   cluster_rows = TRUE, 
                   cluster_columns = TRUE,  
                   detailedresults = FALSE, 
                   legend_position="right",
                   titlesize=20)
Warning: Heatmap/annotation names are duplicated: pearson's coefficient
Warning: Heatmap/annotation names are duplicated: pearson's coefficient, pearson's coefficient
Warning: `legend_height` you specified is too small, use the default minimal height.
Warning: `legend_height` you specified is too small, use the default minimal height.
Warning: `legend_height` you specified is too small, use the default minimal height.

Expression Heatmaps

ROC/AUC

Cohen’s d

PCA with genes from signature only


#' @importFrom edgeR DGEList
#' @importFrom stats prcomp
#' @import ggplot2
#' @importFrom ggpubr ggarrange

plotPCA <- function(data, metadata, genes=NULL, scale=FALSE, center=TRUE, PCs=list(c(1,2)), ColorVariable=NULL,ColorValues=NULL,pointSize=5,legend_nrow=2, legend_position=c("bottom","top","right","left"),ncol=NULL, nrow=NULL){
  
  legend_position <- match.arg(legend_position)
  
  if (is.null(genes)){
    genes <-  row.names(data)
  }
  
  data <- data[row.names(data) %in% genes, , drop=F]
  
  if (!nrow(data)>1) stop(paste0("Error: Number of genes should be >1; In your data you have only found the gene ",genes))
  
  # Ensure metadata matches sample order if provided
  if (!is.null(metadata)) {
    colnames(metadata)[1] <- "Sample"
    rownames(metadata) <- metadata$Sample
    metadata <- metadata[colnames(data), , drop = FALSE]
    y <- edgeR::DGEList( log2(data+1), samples= metadata)  
  } else {
    y <- edgeR::DGEList( log2(data+1))  
  }
  

  
  nPCs <- max(unlist(PCs)) # get the maximum number of PC based on the user's choice
    
  PCAdata <- stats::prcomp(t(y$counts), scale=scale, center=center)
  PCAcounts <- PCAdata$x
  PCAcounts <- as.data.frame(PCAcounts)
  
  if (nPCs > ncol(PCAcounts)) stop("Error: Number of genes too low for number of chosen PCs. Please reduce number of PCs.")
      
  PCAcounts <-  cbind(PCAcounts[,1:nPCs],y$samples) 
  
  pltList <- list()
  
  for (pc in PCs){
    pc <- unlist(pc)
    ev = PCAdata$sdev^2
    pc_x <- round(100*ev[pc[1]]/sum(ev),2) 
    pc_y <- round(100*ev[pc[2]]/sum(ev),2) 
    
    plt <- ggplot2::ggplot(PCAcounts, ggplot2::aes_string(y = paste0("PC",pc[1]), x =  paste0("PC",pc[2])))
    
    
    # Add jittered points, optionally colored by ColorVariable.Default: Brewer Pallette "Paired"
    if (!is.null(ColorVariable)) {
      plt <- plt + ggplot2::geom_point(ggplot2::aes_string(fill = ColorVariable), size = pointSize, alpha = 0.5, shape=21, color="black")
    } else {
      plt <- plt + ggplot2::geom_point(size = pointSize, alpha = 0.5, shape=21, color="black", fill="#D8D8D8")  
    }
    
    # If ColorValues is provided, use a manual color scale; otherwise, if ColorVariable is provided,
    # use a default brewer palette.
    if (!is.null(ColorValues)) {
      plt <- plt + ggplot2::scale_fill_manual(values = ColorValues)
    } else if (!is.null(ColorVariable)) {
      plt <- plt + ggplot2::scale_fill_brewer(palette = "Paired")
    } 
    
    # Add axis labels (including variance)
    
    xlab <- paste0("PC",pc[1],": ",pc_x,"% variance")
    ylab <- paste0("PC",pc[2],": ",pc_y,"% variance")
    titleplot <- paste0("PC",pc[1], " vs PC",pc[2])
    
    plt <- plt + ggplot2::labs(fill = "", x = xlab, y = ylab, title=titleplot)
    
    # Change theme
    plt <- plt +
      ggplot2::theme_bw()+
    ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust=1),
          plot.title = ggplot2::element_text(hjust = 0.5),
          legend.position = "bottom")
    
    # Adjust legend rows if legend_nrow is specified
    if (!is.null(legend_nrow)) {
      plt <- plt + ggplot2::guides(fill = ggplot2::guide_legend(nrow = legend_nrow, position = legend_position))
    }
    
    # Add reference lines
    plt <- plt +
      ggplot2::geom_vline(xintercept=0, linetype="dotted") + 
      ggplot2::geom_hline(yintercept=0, linetype="dotted")  
    
    
    pltList <- c(pltList, list(plt))
    
    
    
  }
  
  n <- length(pltList)
  
  if(n==1){
    plt <- pltList[[1]]
  } else {
    
    # Determine grid layout
    if (is.null(ncol) && is.null(nrow)) {
      
      ncol <- ceiling(sqrt(n))
      nrow <- ceiling(n / ncol)
      
    } else if (is.null(ncol)){
      
      ncol <- ceiling(n / nrow)
      
    } else if (is.null(nrow)){
      
      nrow <- ceiling(n / ncol)
      
    }
    
    if (!is.null(ColorVariable)) {
      plt <- ggpubr::ggarrange(plotlist = pltList, ncol = ncol, nrow = nrow, common.legend = TRUE, align = "h")
    } else {
      plt <- ggpubr::ggarrange(plotlist = pltList, ncol = ncol, nrow = nrow, align = "h")
    }
    
    
    
  }
  
  print(plt)
  
  invisible(list(plt = plt,
              data = PCAcounts))
  
}
LS0tCnRpdGxlOiAiRGVidWdnaW5nIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIEltcG9ydCBEYXRhCgpgYGB7cn0KY29ycmNvdW50c19tZXJnZSA8LSByZWFkUkRTKCJ+L1ZlcnNpb25Db250cm9sL3NlbmVzY2VuY2VfYmVuY2htYXJraW5nL0RhdGEvY29ycmNvdW50c19tZXJnZS5yZHMiKQptZXRhZGF0YV9tZXJnZSA8LSByZWFkUkRTKCJ+L1ZlcnNpb25Db250cm9sL3NlbmVzY2VuY2VfYmVuY2htYXJraW5nL0RhdGEvbWV0YWRhdGFfbWVyZ2UucmRzIikKU2VuZXNjZW5jZVNpZ25hdHVyZXMgPC0gcmVhZFJEUygifi9WZXJzaW9uQ29udHJvbC9zZW5lc2NlbmNlX2JlbmNobWFya2luZy9Db21tb25GaWxlcy9TZW5lc2NlbmNlU2lnbmF0dXJlc19kaXZpZGVkX25ld0NlbGxBZ2UuUkRTIikKYGBgCgpgYGB7cn0KbGlicmFyeShtYXJrZVIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnZ3B1YnIpCmxpYnJhcnkoZWRnZVIpCj9tYXJrZVIKYGBgCgojIFNjb3JlcyAKCmBgYHtyfQo/Q2FsY3VsYXRlU2NvcmVzCmBgYAoKYGBge3IgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTZ9CmRmX3NzR1NFQSA8LSBDYWxjdWxhdGVTY29yZXMoZGF0YSA9IGNvcnJjb3VudHNfbWVyZ2UsIG1ldGFkYXRhID0gbWV0YWRhdGFfbWVyZ2UsIG1ldGhvZCA9ICJzc0dTRUEiLCBnZW5lX3NldHMgPSBTZW5lc2NlbmNlU2lnbmF0dXJlcykKCnNlbmVzY2VuY2VfdHJpZ2dlcnNfY29sb3JzIDwtIGMoCiAgIm5vbmUiID0gIiNFNTczNzMiLCAgIyBTb2Z0IHJlZCAgCiAgIlJhZGlhdGlvbiIgPSAiI0JEQkRCRCIsICAjIE1lZGl1bSBncmF5ICAKICAiRE5BIGRhbWFnZSIgPSAiIzY0QjVGNiIsICAjIEJyaWdodGVyIGJsdWUgIAogICJUZWxvbWVyZSBzaG9ydGVuaW5nIiA9ICIjNEZDM0Y3IiwgICMgVml2aWQgc2t5IGJsdWUgIAogICJETkEgZGVtZXRoeWxhdGlvbiIgPSAiI0JBNjhDOCIsICAjIFJpY2ggbGF2ZW5kZXIgIAogICJPeGlkYXRpdmUgc3RyZXNzIiA9ICIjRkREODM1IiwgICMgU3Ryb25nIHllbGxvdyAgCiAgIkNvbmRpdGlvbmVkIE1lZGl1bSIgPSAiI0YyOTk0QSIsICAjIFdhcm0gb3JhbmdlICAKICAiT25jb2dlbmUiID0gIiM4MUM3ODQiLCAgIyBNZWRpdW0gZ3JlZW4gIAogICJMaXBpZCBBY2N1bXVsYXRpb24iID0gIiNFNTczNzMiLCAgIyBDb3JhbCAgCiAgIkNhbGNpdW0gaW5mbHV4IiA9ICIjMjZBNjlBIiwgICMgRGVlcCB0ZWFsICAKICAiUGxhc21hIG1lbWJyYW5lIGR5c3J1cHRpb24iID0gIiNEMzJGMkYiLCAgIyBTdHJvbmcgc2FsbW9uICAKICAiT1NLTSBmYWN0b3JzIiA9ICIjRkZCNzREIiwgICMgQnJpZ2h0IHBlYWNoICAKICAiWUFQIEtPIiA9ICIjOTU3NUNEIiAgIyBEZWVwIHBhc3RlbCBwdXJwbGUgIAopCgpjZWxsVHlwZXNfY29sb3JzIDwtIGMoCiAgIkZpYnJvYmxhc3QiID0gIiNGRjY5NjEiLCAgICMgU3Ryb25nIFBhc3RlbCBSZWQgIAogICJLZXJhdGlub2N5dGUiID0gIiNGRkIzNDciLCAjIFN0cm9uZyBQYXN0ZWwgT3JhbmdlICAKICAiTWVsYW5vY3l0ZSIgPSAiI0ZGRDcwMCIsICAgIyBTdHJvbmcgUGFzdGVsIFllbGxvdyAgCiAgIkVuZG90aGVsaWFsIiA9ICIjNzdERDc3IiwgICMgU3Ryb25nIFBhc3RlbCBHcmVlbiAgCiAgIk5ldXJvbmFsIiA9ICIjNzc5RUNCIiwgICAgICMgU3Ryb25nIFBhc3RlbCBCbHVlICAKICAiTWVzZW5jaHltYWwiID0gIiNDMjdCQTAiICAgIyBTdHJvbmcgUGFzdGVsIFB1cnBsZSAgCikKCmNvbmRfY29oZW5kIDwtIGxpc3QoQT1jKCJTZW5lc2NlbnQiKSwgIyBpZiBubyB2YXJpYWJsZSBpcyBkZWZpbmVkLCB3aWxsIGJlIHRoZSBmaXJzdCB0aGF0IGFwcGVhcnMgaW4gdGhlIGdncGxvdAogICAgICAgICAgICAgICAgICAgIEI9YygiUHJvbGlmZXJhdGl2ZSIsIlF1aWVzY2VudCIpKQoKUGxvdFNjb3JlcyhSZXN1bHRzTGlzdCA9IGRmX3NzR1NFQSwgQ29sb3JWYXJpYWJsZSA9ICJDZWxsVHlwZSIsIEdyb3VwaW5nVmFyaWFibGU9IkNvbmRpdGlvbiIsICBtZXRob2QgPSJzc0dTRUEiLCBDb2xvclZhbHVlcyA9IGNlbGxUeXBlc19jb2xvcnMsIENvbm5lY3RHcm91cHM9VFJVRSwgbmNvbCA9IDYsIG5yb3cgPSAyLCB3aWR0aFRpdGxlPTIwLCB5X2xpbWl0cyA9IE5VTEwsIGxlZ2VuZF9ucm93ID0gMixjb25kX2NvaGVuZD1jb25kX2NvaGVuZCkKCmBgYAoKYGBge3IgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTZ9CmRmX2xvZ21lZGlhbiA8LSBDYWxjdWxhdGVTY29yZXMoZGF0YSA9IGNvcnJjb3VudHNfbWVyZ2UsIG1ldGFkYXRhID0gbWV0YWRhdGFfbWVyZ2UsIG1ldGhvZCA9ICJsb2dtZWRpYW4iLCBnZW5lX3NldHMgPSBTZW5lc2NlbmNlU2lnbmF0dXJlcykKCnNlbmVzY2VuY2VfdHJpZ2dlcnNfY29sb3JzIDwtIGMoCiAgIm5vbmUiID0gIiNFNTczNzMiLCAgIyBTb2Z0IHJlZCAgCiAgIlJhZGlhdGlvbiIgPSAiI0JEQkRCRCIsICAjIE1lZGl1bSBncmF5ICAKICAiRE5BIGRhbWFnZSIgPSAiIzY0QjVGNiIsICAjIEJyaWdodGVyIGJsdWUgIAogICJUZWxvbWVyZSBzaG9ydGVuaW5nIiA9ICIjNEZDM0Y3IiwgICMgVml2aWQgc2t5IGJsdWUgIAogICJETkEgZGVtZXRoeWxhdGlvbiIgPSAiI0JBNjhDOCIsICAjIFJpY2ggbGF2ZW5kZXIgIAogICJPeGlkYXRpdmUgc3RyZXNzIiA9ICIjRkREODM1IiwgICMgU3Ryb25nIHllbGxvdyAgCiAgIkNvbmRpdGlvbmVkIE1lZGl1bSIgPSAiI0YyOTk0QSIsICAjIFdhcm0gb3JhbmdlICAKICAiT25jb2dlbmUiID0gIiM4MUM3ODQiLCAgIyBNZWRpdW0gZ3JlZW4gIAogICJMaXBpZCBBY2N1bXVsYXRpb24iID0gIiNFNTczNzMiLCAgIyBDb3JhbCAgCiAgIkNhbGNpdW0gaW5mbHV4IiA9ICIjMjZBNjlBIiwgICMgRGVlcCB0ZWFsICAKICAiUGxhc21hIG1lbWJyYW5lIGR5c3J1cHRpb24iID0gIiNEMzJGMkYiLCAgIyBTdHJvbmcgc2FsbW9uICAKICAiT1NLTSBmYWN0b3JzIiA9ICIjRkZCNzREIiwgICMgQnJpZ2h0IHBlYWNoICAKICAiWUFQIEtPIiA9ICIjOTU3NUNEIiAgIyBEZWVwIHBhc3RlbCBwdXJwbGUgIAopCgpjZWxsVHlwZXNfY29sb3JzIDwtIGMoCiAgIkZpYnJvYmxhc3QiID0gIiNGRjY5NjEiLCAgICMgU3Ryb25nIFBhc3RlbCBSZWQgIAogICJLZXJhdGlub2N5dGUiID0gIiNGRkIzNDciLCAjIFN0cm9uZyBQYXN0ZWwgT3JhbmdlICAKICAiTWVsYW5vY3l0ZSIgPSAiI0ZGRDcwMCIsICAgIyBTdHJvbmcgUGFzdGVsIFllbGxvdyAgCiAgIkVuZG90aGVsaWFsIiA9ICIjNzdERDc3IiwgICMgU3Ryb25nIFBhc3RlbCBHcmVlbiAgCiAgIk5ldXJvbmFsIiA9ICIjNzc5RUNCIiwgICAgICMgU3Ryb25nIFBhc3RlbCBCbHVlICAKICAiTWVzZW5jaHltYWwiID0gIiNDMjdCQTAiICAgIyBTdHJvbmcgUGFzdGVsIFB1cnBsZSAgCikKCmNvbmRfY29oZW5kIDwtIGxpc3QoQT1jKCJTZW5lc2NlbnQiKSwgIyBpZiBubyB2YXJpYWJsZSBpcyBkZWZpbmVkLCB3aWxsIGJlIHRoZSBmaXJzdCB0aGF0IGFwcGVhcnMgaW4gdGhlIGdncGxvdAogICAgICAgICAgICAgICAgICAgIEI9YygiUHJvbGlmZXJhdGl2ZSIsIlF1aWVzY2VudCIpKQoKUGxvdFNjb3JlcyhSZXN1bHRzTGlzdCA9IGRmX2xvZ21lZGlhbiwgQ29sb3JWYXJpYWJsZSA9ICJDZWxsVHlwZSIsIEdyb3VwaW5nVmFyaWFibGU9IkNvbmRpdGlvbiIsICBtZXRob2QgPSJsb2dtZWRpYW4iLCBDb2xvclZhbHVlcyA9IGNlbGxUeXBlc19jb2xvcnMsIENvbm5lY3RHcm91cHM9VFJVRSwgbmNvbCA9IDYsIG5yb3cgPSAyLCB3aWR0aFRpdGxlPTIwLCB5X2xpbWl0cyA9IE5VTEwsIGxlZ2VuZF9ucm93ID0gMix4bGFiPU5VTEwsIGNvbmRfY29oZW5kID0gY29uZF9jb2hlbmQpCgpgYGAKCmBgYHtyIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD02fQpkZl9yYW5raW5nIDwtIENhbGN1bGF0ZVNjb3JlcyhkYXRhID0gY29ycmNvdW50c19tZXJnZSwgbWV0YWRhdGEgPSBtZXRhZGF0YV9tZXJnZSwgbWV0aG9kID0gInJhbmtpbmciLCBnZW5lX3NldHMgPSBTZW5lc2NlbmNlU2lnbmF0dXJlcykKCnNlbmVzY2VuY2VfdHJpZ2dlcnNfY29sb3JzIDwtIGMoCiAgIm5vbmUiID0gIiNFNTczNzMiLCAgIyBTb2Z0IHJlZCAgCiAgIlJhZGlhdGlvbiIgPSAiI0JEQkRCRCIsICAjIE1lZGl1bSBncmF5ICAKICAiRE5BIGRhbWFnZSIgPSAiIzY0QjVGNiIsICAjIEJyaWdodGVyIGJsdWUgIAogICJUZWxvbWVyZSBzaG9ydGVuaW5nIiA9ICIjNEZDM0Y3IiwgICMgVml2aWQgc2t5IGJsdWUgIAogICJETkEgZGVtZXRoeWxhdGlvbiIgPSAiI0JBNjhDOCIsICAjIFJpY2ggbGF2ZW5kZXIgIAogICJPeGlkYXRpdmUgc3RyZXNzIiA9ICIjRkREODM1IiwgICMgU3Ryb25nIHllbGxvdyAgCiAgIkNvbmRpdGlvbmVkIE1lZGl1bSIgPSAiI0YyOTk0QSIsICAjIFdhcm0gb3JhbmdlICAKICAiT25jb2dlbmUiID0gIiM4MUM3ODQiLCAgIyBNZWRpdW0gZ3JlZW4gIAogICJMaXBpZCBBY2N1bXVsYXRpb24iID0gIiNFNTczNzMiLCAgIyBDb3JhbCAgCiAgIkNhbGNpdW0gaW5mbHV4IiA9ICIjMjZBNjlBIiwgICMgRGVlcCB0ZWFsICAKICAiUGxhc21hIG1lbWJyYW5lIGR5c3J1cHRpb24iID0gIiNEMzJGMkYiLCAgIyBTdHJvbmcgc2FsbW9uICAKICAiT1NLTSBmYWN0b3JzIiA9ICIjRkZCNzREIiwgICMgQnJpZ2h0IHBlYWNoICAKICAiWUFQIEtPIiA9ICIjOTU3NUNEIiAgIyBEZWVwIHBhc3RlbCBwdXJwbGUgIAopCgpjZWxsVHlwZXNfY29sb3JzIDwtIGMoCiAgIkZpYnJvYmxhc3QiID0gIiNGRjY5NjEiLCAgICMgU3Ryb25nIFBhc3RlbCBSZWQgIAogICJLZXJhdGlub2N5dGUiID0gIiNGRkIzNDciLCAjIFN0cm9uZyBQYXN0ZWwgT3JhbmdlICAKICAiTWVsYW5vY3l0ZSIgPSAiI0ZGRDcwMCIsICAgIyBTdHJvbmcgUGFzdGVsIFllbGxvdyAgCiAgIkVuZG90aGVsaWFsIiA9ICIjNzdERDc3IiwgICMgU3Ryb25nIFBhc3RlbCBHcmVlbiAgCiAgIk5ldXJvbmFsIiA9ICIjNzc5RUNCIiwgICAgICMgU3Ryb25nIFBhc3RlbCBCbHVlICAKICAiTWVzZW5jaHltYWwiID0gIiNDMjdCQTAiICAgIyBTdHJvbmcgUGFzdGVsIFB1cnBsZSAgCikKCmNvbmRfY29oZW5kIDwtIGxpc3QoQT1jKCJTZW5lc2NlbnQiKSwgIyBpZiBubyB2YXJpYWJsZSBpcyBkZWZpbmVkLCB3aWxsIGJlIHRoZSBmaXJzdCB0aGF0IGFwcGVhcnMgaW4gdGhlIGdncGxvdAogICAgICAgICAgICAgICAgICAgIEI9YygiUHJvbGlmZXJhdGl2ZSIsIlF1aWVzY2VudCIpKQoKUGxvdFNjb3JlcyhSZXN1bHRzTGlzdCA9IGRmX3JhbmtpbmcsIENvbG9yVmFyaWFibGUgPSAiQ2VsbFR5cGUiLCBHcm91cGluZ1ZhcmlhYmxlPSJDb25kaXRpb24iLCAgbWV0aG9kID0icmFua2luZyIsIENvbG9yVmFsdWVzID0gY2VsbFR5cGVzX2NvbG9ycywgQ29ubmVjdEdyb3Vwcz1UUlVFLCBuY29sID0gNiwgbnJvdyA9IDIsIHdpZHRoVGl0bGU9MjAsIHlfbGltaXRzID0gTlVMTCwgbGVnZW5kX25yb3cgPSAyLHhsYWI9TlVMTCwgY29uZF9jb2hlbmQgPSBjb25kX2NvaGVuZCkKCmBgYAoKYGBge3J9CndyYXBfdGl0bGVfYXV4IDwtIGZ1bmN0aW9uKHRpdGxlLCB3aWR0aCA9IDMwKSB7CiAgaWYgKG5jaGFyKHRpdGxlKSA8PSB3aWR0aCkgewogICAgcmV0dXJuKHRpdGxlKSAgIyBObyBuZWVkIHRvIHdyYXAgaWYgaXQgZml0cwogIH0KICAKICB3cmFwcGVkX3RpdGxlIDwtICIiCiAgd2hpbGUgKG5jaGFyKHRpdGxlKSA+IHdpZHRoKSB7CiAgICAjIEZpbmQgcG9zaXRpb25zIG9mIGNhcGl0YWwgbGV0dGVycyBhbmQgc3ltYm9scyBuZWFyIHRoZSB3cmFwIHBvaW50CiAgICBjYXBpdGFsX3BvcyA8LSBncmVnZXhwcigiW0EtWl0iLCB0aXRsZSlbWzFdXQogICAgc3ltYm9sX3BvcyA8LSBncmVnZXhwcigiKF98LXw6KSIsIHRpdGxlKVtbMV1dCiAgICAKICAgICMgQ2hlY2sgZm9yIHN5bWJvbCBicmVha3Mgd2l0aGluIHRoZSBsYXN0IGZldyBjaGFyYWN0ZXJzICh3aWR0aCAtIDUgdG8gd2lkdGgpCiAgICB2YWxpZF9zeW1ib2xfYnJlYWtzIDwtIHN5bWJvbF9wb3Nbc3ltYm9sX3BvcyA+PSAod2lkdGggLSA1KSAmIHN5bWJvbF9wb3MgPD0gd2lkdGhdCiAgICAKICAgIGlmIChsZW5ndGgodmFsaWRfc3ltYm9sX2JyZWFrcykgPiAwKSB7CiAgICAgICMgSWYgYSBzdWl0YWJsZSBzeW1ib2wgaXMgZm91bmQsIGJyZWFrIGF0IHRoZSBmaXJzdCB2YWxpZCBzeW1ib2wKICAgICAgYnJlYWtfYXQgPC0gdmFsaWRfc3ltYm9sX2JyZWFrc1sxXQogICAgfSBlbHNlIHsKICAgICAgIyBJZiBubyBzdWl0YWJsZSBzeW1ib2wsIGxvb2sgZm9yIGNhcGl0YWwgbGV0dGVycyB3aXRoaW4gdGhlIHNhbWUgcmFuZ2UKICAgICAgdmFsaWRfY2FwaXRhbF9icmVha3MgPC0gY2FwaXRhbF9wb3NbY2FwaXRhbF9wb3MgPj0gKHdpZHRoIC0gNSkgJiBjYXBpdGFsX3BvcyA8PSB3aWR0aF0KICAgICAgCiAgICAgIGlmIChsZW5ndGgodmFsaWRfY2FwaXRhbF9icmVha3MpID4gMCkgewogICAgICAgICMgSWYgYSBjYXBpdGFsIGxldHRlciBpcyBmb3VuZCwgYnJlYWsganVzdCBiZWZvcmUgdGhlIGNhcGl0YWwgbGV0dGVyCiAgICAgICAgYnJlYWtfYXQgPC0gdmFsaWRfY2FwaXRhbF9icmVha3NbMV0gLSAxCiAgICAgIH0gZWxzZSB7CiAgICAgICAgIyBJZiBubyBzdWl0YWJsZSBzeW1ib2wgb3IgY2FwaXRhbCBsZXR0ZXIsIGJyZWFrIGF0IHdpZHRoCiAgICAgICAgYnJlYWtfYXQgPC0gd2lkdGgKICAgICAgfQogICAgfQogICAgCiAgICAjIEFwcGVuZCB0aGUgd3JhcHBlZCBsaW5lCiAgICB3cmFwcGVkX3RpdGxlIDwtIHBhc3RlMCh3cmFwcGVkX3RpdGxlLCBzdWJzdHIodGl0bGUsIDEsIGJyZWFrX2F0KSwgIlxuIikKICAgIAogICAgIyBVcGRhdGUgdGl0bGUgd2l0aCB0aGUgcmVtYWluaW5nIHRleHQgYWZ0ZXIgdGhlIGJyZWFrCiAgICB0aXRsZSA8LSBzdWJzdHIodGl0bGUsIGJyZWFrX2F0ICsgMSwgbmNoYXIodGl0bGUpKQogIH0KICAKICAjIEFkZCB0aGUgcmVtYWluaW5nIHBhcnQgb2YgdGhlIHRpdGxlCiAgd3JhcHBlZF90aXRsZSA8LSBwYXN0ZTAod3JhcHBlZF90aXRsZSwgdGl0bGUpCiAgCiAgcmV0dXJuKHdyYXBwZWRfdGl0bGUpCn0KYGBgCgoKYGBge3IgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTh9CgpwbG90bGlzdCA8LSBsaXN0KCkKCmZvciAoc2lnIGluIG5hbWVzKGRmX3NzR1NFQSkpewogIAogIGRmX3N1YnNldF9zc0dTRUEgPC0gZGZfc3NHU0VBW1tzaWddXQogIGRmX3N1YnNldF9sb2dtZWRpYW4gPC0gZGZfbG9nbWVkaWFuW1tzaWddXQogIAogIGRmX3N1YnNldF9tZXJnZSA8LSBtZXJnZShkZl9zdWJzZXRfc3NHU0VBLGRmX3N1YnNldF9sb2dtZWRpYW4sYnk9InNhbXBsZSIpCiAgCiAgIyBXcmFwIHRoZSBzaWduYXR1cmUgbmFtZSB1c2luZyB0aGUgaGVscGVyIGZ1bmN0aW9uCiAgd3JhcHBlZF90aXRsZSA8LSB3cmFwX3RpdGxlX2F1eChzaWcsIHdpZHRoID0gMjApICAKICAKICBwbG90bGlzdFtbc2lnXV0gPC0gZ2dwbG90Mjo6Z2dwbG90KGRmX3N1YnNldF9tZXJnZSwgYWVzKHg9c2NvcmUueCwgeT1zY29yZS55KSkgKwogICAgZ2VvbV9wb2ludChzaXplPTQsIGFscGhhPTAuOCwgZmlsbD0iZGFya2dyZXkiLCBzaGFwZT0yMSkgKwogICAgdGhlbWVfYncoKSArCiAgICB4bGFiKCJzc0dTRUEgRW5yaWNobWVudCBTY29yZSIpICsKICAgIHlsYWIoIk5vcm1hbGlzZWQgU2lnbmF0dXJlIFNjb3JlIikgKwogICAgZ2d0aXRsZSh3cmFwcGVkX3RpdGxlKSArCiAgICB0aGVtZShwbG90LnRpdGxlID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBzaXplPTEwKSwKICAgICAgICAgIHBsb3Quc3VidGl0bGUgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSAKICAKfQoKZ2dwdWJyOjpnZ2FycmFuZ2UocGxvdGxpc3Q9cGxvdGxpc3QsIG5yb3c9MywgbmNvbD00LCBhbGlnbiA9ICJoIikKYGBgCgojIyBCaWRpcmVjdGlvbmFsIGdlbmUgc2lnbmF0dXJlcwoKVHJ5IHNjb3JlcyB3aXRoIGJpZGlyZWN0aW9uYWwgc2lnbmF0dXJlcwoKYGBge3J9CmJpZGlyZWN0c2lncyA8LSByZWFkUkRTKCJ+L1ZlcnNpb25Db250cm9sL3NlbmVzY2VuY2VfYmVuY2htYXJraW5nL0NvbW1vbkZpbGVzL1NlbmVzY2VuY2VTaWduYXR1cmVzX2NvbXBsZXRlX25ld0NlbGxBZ2UuUkRTIikKZm9yIChzaWcgaW4gbmFtZXMoYmlkaXJlY3RzaWdzKSl7CiAgc2lnZGYgPC0gYmlkaXJlY3RzaWdzW1tzaWddXQogIHNpZ2RmIDwtIHNpZ2RmWywxOjJdICMgcmVtb3ZlIHRoZSB0aGlyZCBjb2x1bW4sIGlmIGFwcGxpY2FibGUKICBpZihhbnkoc2lnZGZbLDJdPT0ibm90X3JlcG9ydGVkIikpewogICAgc2lnZGYgPC0gc2lnZGZbLDFdCiAgICBiaWRpcmVjdHNpZ3NbW3NpZ11dIDwtIHNpZ2RmCiAgICBuZXh0IAogIH0KICBzaWdkZlssMl0gPC0gaWZlbHNlKHNpZ2RmWywyXT09ImVucmljaGVkIiwxLC0xKQogIGJpZGlyZWN0c2lnc1tbc2lnXV0gPC0gc2lnZGYKfQpiaWRpcmVjdHNpZ3MKCmBgYAoKYGBge3IgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9MTB9CmRmX2xvZ21lZGlhbiA8LSBDYWxjdWxhdGVTY29yZXMoZGF0YSA9IGNvcnJjb3VudHNfbWVyZ2UsIG1ldGFkYXRhID0gbWV0YWRhdGFfbWVyZ2UsIG1ldGhvZCA9ICJsb2dtZWRpYW4iLCBnZW5lX3NldHMgPSBiaWRpcmVjdHNpZ3MpCgpzZW5lc2NlbmNlX3RyaWdnZXJzX2NvbG9ycyA8LSBjKAogICJub25lIiA9ICIjRTU3MzczIiwgICMgU29mdCByZWQgIAogICJSYWRpYXRpb24iID0gIiNCREJEQkQiLCAgIyBNZWRpdW0gZ3JheSAgCiAgIkROQSBkYW1hZ2UiID0gIiM2NEI1RjYiLCAgIyBCcmlnaHRlciBibHVlICAKICAiVGVsb21lcmUgc2hvcnRlbmluZyIgPSAiIzRGQzNGNyIsICAjIFZpdmlkIHNreSBibHVlICAKICAiRE5BIGRlbWV0aHlsYXRpb24iID0gIiNCQTY4QzgiLCAgIyBSaWNoIGxhdmVuZGVyICAKICAiT3hpZGF0aXZlIHN0cmVzcyIgPSAiI0ZERDgzNSIsICAjIFN0cm9uZyB5ZWxsb3cgIAogICJDb25kaXRpb25lZCBNZWRpdW0iID0gIiNGMjk5NEEiLCAgIyBXYXJtIG9yYW5nZSAgCiAgIk9uY29nZW5lIiA9ICIjODFDNzg0IiwgICMgTWVkaXVtIGdyZWVuICAKICAiTGlwaWQgQWNjdW11bGF0aW9uIiA9ICIjRTU3MzczIiwgICMgQ29yYWwgIAogICJDYWxjaXVtIGluZmx1eCIgPSAiIzI2QTY5QSIsICAjIERlZXAgdGVhbCAgCiAgIlBsYXNtYSBtZW1icmFuZSBkeXNydXB0aW9uIiA9ICIjRDMyRjJGIiwgICMgU3Ryb25nIHNhbG1vbiAgCiAgIk9TS00gZmFjdG9ycyIgPSAiI0ZGQjc0RCIsICAjIEJyaWdodCBwZWFjaCAgCiAgIllBUCBLTyIgPSAiIzk1NzVDRCIgICMgRGVlcCBwYXN0ZWwgcHVycGxlICAKKQoKY2VsbFR5cGVzX2NvbG9ycyA8LSBjKAogICJGaWJyb2JsYXN0IiA9ICIjRkY2OTYxIiwgICAjIFN0cm9uZyBQYXN0ZWwgUmVkICAKICAiS2VyYXRpbm9jeXRlIiA9ICIjRkZCMzQ3IiwgIyBTdHJvbmcgUGFzdGVsIE9yYW5nZSAgCiAgIk1lbGFub2N5dGUiID0gIiNGRkQ3MDAiLCAgICMgU3Ryb25nIFBhc3RlbCBZZWxsb3cgIAogICJFbmRvdGhlbGlhbCIgPSAiIzc3REQ3NyIsICAjIFN0cm9uZyBQYXN0ZWwgR3JlZW4gIAogICJOZXVyb25hbCIgPSAiIzc3OUVDQiIsICAgICAjIFN0cm9uZyBQYXN0ZWwgQmx1ZSAgCiAgIk1lc2VuY2h5bWFsIiA9ICIjQzI3QkEwIiAgICMgU3Ryb25nIFBhc3RlbCBQdXJwbGUgIAopCgpjb25kX2NvaGVuZCA8LSBsaXN0KEE9YygiU2VuZXNjZW50IiksICMgaWYgbm8gdmFyaWFibGUgaXMgZGVmaW5lZCwgd2lsbCBiZSB0aGUgZmlyc3QgdGhhdCBhcHBlYXJzIGluIHRoZSBnZ3Bsb3QKICAgICAgICAgICAgICAgICAgICBCPWMoIlByb2xpZmVyYXRpdmUiLCJRdWllc2NlbnQiKSkKClBsb3RTY29yZXMoUmVzdWx0c0xpc3QgPSBkZl9sb2dtZWRpYW4sIENvbG9yVmFyaWFibGUgPSAiQ2VsbFR5cGUiLCBHcm91cGluZ1ZhcmlhYmxlPSJDb25kaXRpb24iLCAgbWV0aG9kID0ibG9nbWVkaWFuIiwgQ29sb3JWYWx1ZXMgPSBjZWxsVHlwZXNfY29sb3JzLCBDb25uZWN0R3JvdXBzPVRSVUUsIG5jb2wgPSAzLCBucm93ID0gMywgd2lkdGhUaXRsZT0yMCwgeV9saW1pdHMgPSBOVUxMLCBsZWdlbmRfbnJvdyA9IDIseGxhYj1OVUxMLCBjb25kX2NvaGVuZCA9IGNvbmRfY29oZW5kKQoKYGBgCgoKYGBge3IgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9MTB9CiAKZGZfc3Nnc2VhIDwtIENhbGN1bGF0ZVNjb3JlcyhkYXRhID0gY29ycmNvdW50c19tZXJnZSwgbWV0YWRhdGEgPSBtZXRhZGF0YV9tZXJnZSwgbWV0aG9kID0gInNzR1NFQSIsIGdlbmVfc2V0cyA9IGJpZGlyZWN0c2lncykKCnNlbmVzY2VuY2VfdHJpZ2dlcnNfY29sb3JzIDwtIGMoCiAgIm5vbmUiID0gIiNFNTczNzMiLCAgIyBTb2Z0IHJlZCAgCiAgIlJhZGlhdGlvbiIgPSAiI0JEQkRCRCIsICAjIE1lZGl1bSBncmF5ICAKICAiRE5BIGRhbWFnZSIgPSAiIzY0QjVGNiIsICAjIEJyaWdodGVyIGJsdWUgIAogICJUZWxvbWVyZSBzaG9ydGVuaW5nIiA9ICIjNEZDM0Y3IiwgICMgVml2aWQgc2t5IGJsdWUgIAogICJETkEgZGVtZXRoeWxhdGlvbiIgPSAiI0JBNjhDOCIsICAjIFJpY2ggbGF2ZW5kZXIgIAogICJPeGlkYXRpdmUgc3RyZXNzIiA9ICIjRkREODM1IiwgICMgU3Ryb25nIHllbGxvdyAgCiAgIkNvbmRpdGlvbmVkIE1lZGl1bSIgPSAiI0YyOTk0QSIsICAjIFdhcm0gb3JhbmdlICAKICAiT25jb2dlbmUiID0gIiM4MUM3ODQiLCAgIyBNZWRpdW0gZ3JlZW4gIAogICJMaXBpZCBBY2N1bXVsYXRpb24iID0gIiNFNTczNzMiLCAgIyBDb3JhbCAgCiAgIkNhbGNpdW0gaW5mbHV4IiA9ICIjMjZBNjlBIiwgICMgRGVlcCB0ZWFsICAKICAiUGxhc21hIG1lbWJyYW5lIGR5c3J1cHRpb24iID0gIiNEMzJGMkYiLCAgIyBTdHJvbmcgc2FsbW9uICAKICAiT1NLTSBmYWN0b3JzIiA9ICIjRkZCNzREIiwgICMgQnJpZ2h0IHBlYWNoICAKICAiWUFQIEtPIiA9ICIjOTU3NUNEIiAgIyBEZWVwIHBhc3RlbCBwdXJwbGUgIAopCgpjZWxsVHlwZXNfY29sb3JzIDwtIGMoCiAgIkZpYnJvYmxhc3QiID0gIiNGRjY5NjEiLCAgICMgU3Ryb25nIFBhc3RlbCBSZWQgIAogICJLZXJhdGlub2N5dGUiID0gIiNGRkIzNDciLCAjIFN0cm9uZyBQYXN0ZWwgT3JhbmdlICAKICAiTWVsYW5vY3l0ZSIgPSAiI0ZGRDcwMCIsICAgIyBTdHJvbmcgUGFzdGVsIFllbGxvdyAgCiAgIkVuZG90aGVsaWFsIiA9ICIjNzdERDc3IiwgICMgU3Ryb25nIFBhc3RlbCBHcmVlbiAgCiAgIk5ldXJvbmFsIiA9ICIjNzc5RUNCIiwgICAgICMgU3Ryb25nIFBhc3RlbCBCbHVlICAKICAiTWVzZW5jaHltYWwiID0gIiNDMjdCQTAiICAgIyBTdHJvbmcgUGFzdGVsIFB1cnBsZSAgCikKCmNvbmRfY29oZW5kIDwtIGxpc3QoQT1jKCJTZW5lc2NlbnQiKSwgIyBpZiBubyB2YXJpYWJsZSBpcyBkZWZpbmVkLCB3aWxsIGJlIHRoZSBmaXJzdCB0aGF0IGFwcGVhcnMgaW4gdGhlIGdncGxvdAogICAgICAgICAgICAgICAgICAgIEI9YygiUHJvbGlmZXJhdGl2ZSIsIlF1aWVzY2VudCIpKQoKUGxvdFNjb3JlcyhSZXN1bHRzTGlzdCA9IGRmX3NzZ3NlYSwgQ29sb3JWYXJpYWJsZSA9ICJDZWxsVHlwZSIsIEdyb3VwaW5nVmFyaWFibGU9IkNvbmRpdGlvbiIsICBtZXRob2QgPSJzc0dTRUEiLCBDb2xvclZhbHVlcyA9IGNlbGxUeXBlc19jb2xvcnMsIENvbm5lY3RHcm91cHM9VFJVRSwgbmNvbCA9IDMsIG5yb3cgPSAzLCB3aWR0aFRpdGxlPTIwLCB5X2xpbWl0cyA9IE5VTEwsIGxlZ2VuZF9ucm93ID0gMix4bGFiPU5VTEwsIGNvbmRfY29oZW5kID0gY29uZF9jb2hlbmQpCgpgYGAKCgoKCmBgYHtyIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTEwfQogCmRmX3JhbmtpbmcgPC0gQ2FsY3VsYXRlU2NvcmVzKGRhdGEgPSBjb3JyY291bnRzX21lcmdlLCBtZXRhZGF0YSA9IG1ldGFkYXRhX21lcmdlLCBtZXRob2QgPSAicmFua2luZyIsIGdlbmVfc2V0cyA9IGJpZGlyZWN0c2lncykKCnNlbmVzY2VuY2VfdHJpZ2dlcnNfY29sb3JzIDwtIGMoCiAgIm5vbmUiID0gIiNFNTczNzMiLCAgIyBTb2Z0IHJlZCAgCiAgIlJhZGlhdGlvbiIgPSAiI0JEQkRCRCIsICAjIE1lZGl1bSBncmF5ICAKICAiRE5BIGRhbWFnZSIgPSAiIzY0QjVGNiIsICAjIEJyaWdodGVyIGJsdWUgIAogICJUZWxvbWVyZSBzaG9ydGVuaW5nIiA9ICIjNEZDM0Y3IiwgICMgVml2aWQgc2t5IGJsdWUgIAogICJETkEgZGVtZXRoeWxhdGlvbiIgPSAiI0JBNjhDOCIsICAjIFJpY2ggbGF2ZW5kZXIgIAogICJPeGlkYXRpdmUgc3RyZXNzIiA9ICIjRkREODM1IiwgICMgU3Ryb25nIHllbGxvdyAgCiAgIkNvbmRpdGlvbmVkIE1lZGl1bSIgPSAiI0YyOTk0QSIsICAjIFdhcm0gb3JhbmdlICAKICAiT25jb2dlbmUiID0gIiM4MUM3ODQiLCAgIyBNZWRpdW0gZ3JlZW4gIAogICJMaXBpZCBBY2N1bXVsYXRpb24iID0gIiNFNTczNzMiLCAgIyBDb3JhbCAgCiAgIkNhbGNpdW0gaW5mbHV4IiA9ICIjMjZBNjlBIiwgICMgRGVlcCB0ZWFsICAKICAiUGxhc21hIG1lbWJyYW5lIGR5c3J1cHRpb24iID0gIiNEMzJGMkYiLCAgIyBTdHJvbmcgc2FsbW9uICAKICAiT1NLTSBmYWN0b3JzIiA9ICIjRkZCNzREIiwgICMgQnJpZ2h0IHBlYWNoICAKICAiWUFQIEtPIiA9ICIjOTU3NUNEIiAgIyBEZWVwIHBhc3RlbCBwdXJwbGUgIAopCgpjZWxsVHlwZXNfY29sb3JzIDwtIGMoCiAgIkZpYnJvYmxhc3QiID0gIiNGRjY5NjEiLCAgICMgU3Ryb25nIFBhc3RlbCBSZWQgIAogICJLZXJhdGlub2N5dGUiID0gIiNGRkIzNDciLCAjIFN0cm9uZyBQYXN0ZWwgT3JhbmdlICAKICAiTWVsYW5vY3l0ZSIgPSAiI0ZGRDcwMCIsICAgIyBTdHJvbmcgUGFzdGVsIFllbGxvdyAgCiAgIkVuZG90aGVsaWFsIiA9ICIjNzdERDc3IiwgICMgU3Ryb25nIFBhc3RlbCBHcmVlbiAgCiAgIk5ldXJvbmFsIiA9ICIjNzc5RUNCIiwgICAgICMgU3Ryb25nIFBhc3RlbCBCbHVlICAKICAiTWVzZW5jaHltYWwiID0gIiNDMjdCQTAiICAgIyBTdHJvbmcgUGFzdGVsIFB1cnBsZSAgCikKCmNvbmRfY29oZW5kIDwtIGxpc3QoQT1jKCJTZW5lc2NlbnQiKSwgIyBpZiBubyB2YXJpYWJsZSBpcyBkZWZpbmVkLCB3aWxsIGJlIHRoZSBmaXJzdCB0aGF0IGFwcGVhcnMgaW4gdGhlIGdncGxvdAogICAgICAgICAgICAgICAgICAgIEI9YygiUHJvbGlmZXJhdGl2ZSIsIlF1aWVzY2VudCIpKQoKUGxvdFNjb3JlcyhSZXN1bHRzTGlzdCA9IGRmX3JhbmtpbmcsIENvbG9yVmFyaWFibGUgPSAiQ2VsbFR5cGUiLCBHcm91cGluZ1ZhcmlhYmxlPSJDb25kaXRpb24iLCAgbWV0aG9kID0icmFua2luZyIsIENvbG9yVmFsdWVzID0gY2VsbFR5cGVzX2NvbG9ycywgQ29ubmVjdEdyb3Vwcz1UUlVFLCBuY29sID0gMywgbnJvdyA9IDMsIHdpZHRoVGl0bGU9MjAsIHlfbGltaXRzID0gTlVMTCwgbGVnZW5kX25yb3cgPSAyLHhsYWI9TlVMTCwgY29uZF9jb2hlbmQgPSBjb25kX2NvaGVuZCkKCmBgYAoKCiMjIFJhbmtpbmcgCgpgYGB7cn0KZGF0YSA8LSBjb3JyY291bnRzX21lcmdlCm1ldGFkYXRhIDwtIG1ldGFkYXRhX21lcmdlCmBgYAoKYGBge3J9CgogCgpnZXRSYW5raW5nIDwtIGZ1bmN0aW9uKGRhdGEsc2FtcGxlLCBnZW5lc2V0KXsKICAKICBleHByZXNzaW9uZ2VuZSA8LSBkYXRhWyxzYW1wbGVdICMgaXNvbGF0ZSBvbmUgc2FtcGxlIGFuZCBnZXQgdGhlIGV4cHJlc3Npb24gb2YgYWxsIGdlbmVzCiAgbmFtZXMoZXhwcmVzc2lvbmdlbmUpIDwtIHJvdy5uYW1lcyhkYXRhKSAjIG5hbWUgdmVjdG9yCiAgZXhwcmVzc2lvbmdlbmUgPC0gZXhwcmVzc2lvbmdlbmVbb3JkZXIoZXhwcmVzc2lvbmdlbmUsIGRlY3JlYXNpbmcgPSBGKV0gIyBvcmRlciB2ZWN0b3IgZnJvbSBsZWFzdCBleHByZXNzZWQgdG8gbW9zdCBleHByZXNzZWQKICByYW5raW5nIDwtIG1hdGNoKGdlbmVzZXQsbmFtZXMoZXhwcmVzc2lvbmdlbmUpKSAjIHNlZSB3aGVyZSBnZW5lcyBpbiB0aGUgZ2VuZXNldCBhcHBlYXIgaW4gdGhlIG9yZGVyZWQgbGlzdCBvZiBnZW5lcyAKICByYW5raW5nIDwtIGFzLnZlY3RvcihuYS5vbWl0KHJhbmtpbmcpKSAjIG9taXQgZ2VuZXMgdGhhdCB3ZXJlIG5vdCBmb3VuZAogIAogIHJldHVybihzdW0ocmFua2luZykpICMgcmV0dXJuIGFsc28gbnVtYmVyIG9mIGdlbmVzIGZvdW5kCiAgCn0KCgpDYWxjdWxhdGVTY29yZXNfUmFua2luZyA8LSBmdW5jdGlvbihkYXRhLCBtZXRhZGF0YSA9IE5VTEwsIGdlbmVfc2V0cykgewogIAogIFJlc3VsdHNMaXN0IDwtIGxpc3QoKQogIAogIGlmICghaXMuZGF0YS5mcmFtZShkYXRhKSkgc3RvcCgiRXJyb3I6IGRhdGEgbXVzdCBiZSBhIGRhdGEtZnJhbWUiKQogIGlmICghaXMubnVsbChtZXRhZGF0YSkgJiYgIWlzLmRhdGEuZnJhbWUobWV0YWRhdGEpKSBzdG9wKCJFcnJvcjogbWV0YWRhdGEgbXVzdCBiZSBhIGRhdGEtZnJhbWUiKQogIGlmICghaXMubGlzdChnZW5lX3NldHMpKSBzdG9wKCJFcnJvcjogZ2VuZV9zZXRzIG11c3QgYmUgYSBsaXN0IikKCiAgIyBDaGFuZ2UgZmlyc3QgY29sdW1uIG5hbWUgdG8gZGVmYXVsdCBuYW1lICJzYW1wbGUiLCBmb3IgbWVyZ2luZyBwdXJwb3NlcwogIGlmICghaXMubnVsbChtZXRhZGF0YSkpIGNvbG5hbWVzKG1ldGFkYXRhKVsxXSA8LSAic2FtcGxlIgogIAogICMgZGVmaW5lIHVuaXZlcnNlIG9mIGdlbmVzCiAgdW5pdmVyc2VfZ2VuZXMgPC0gcm93Lm5hbWVzKGRhdGEpCiAgcmFua2luZ19nZW5lcyA8LSBjKDE6bGVuZ3RoKHVuaXZlcnNlX2dlbmVzKSkKICAKICBmb3IgKHNpZyBpbiBuYW1lcyhnZW5lX3NldHMpKSB7CiAgICAKICAgIHNpZ25hdHVyZSA8LSBnZW5lX3NldHNbW3NpZ11dCiAgICAKICAgIGlmIChpcy5kYXRhLmZyYW1lKHNpZ25hdHVyZSkpIHsgICMgSWYgYSBkYXRhIGZyYW1lLCBjaGVjayBlbnJpY2htZW50IGRpcmVjdGlvbgogICAgICAKICAgICAgIyBudW1iZXIgb2YgdmFsdWVzIGluIHRoZSBlbnJpY2htZW50IGNvbHVtbgogICAgICBuYl9mYWN0b3JzIDwtIGxlbmd0aCh1bmlxdWUoc2lnbmF0dXJlWywgMl0pKQogICAgICAKICAgICAgaWYgKG5iX2ZhY3RvcnMgPiAxKSB7ICAjIEJvdGggdXAgKDEpIGFuZCBkb3duICgtMSkgZ2VuZXMKICAgICAgICAKICAgICAgICBtZXNzYWdlKHBhc3RlMCgiQ29uc2lkZXJpbmcgYmlkaXJlY3Rpb25hbCBnZW5lIHNpZ25hdHVyZSBtb2RlIGZvciBzaWduYXR1cmUgIiwgc2lnKSkKICAgICAgICAKICAgICAgICB0b3RhbG5iX2dlbmVzIDwtIHN1bShzaWduYXR1cmVbLDFdICVpbiUgcm93Lm5hbWVzKGRhdGEpKQogICAgICAgIAogICAgICAgIHNpZ25hdHVyZWdlbmVzX3VwIDwtIHNpZ25hdHVyZVtzaWduYXR1cmVbLDJdPT0xLDFdCiAgICAgICAgc2lnbmF0dXJlZ2VuZXNfZG93biA8LSBzaWduYXR1cmVbc2lnbmF0dXJlWywyXT09LTEsMV0KICAgICAgICAKICAgICAgICAjIEFwcGx5IGdldFJhbmtpbmcgZnVuY3Rpb24gdG8gZWFjaCBzYW1wbGUgKGNvbHVtbikKICAgICAgICByYW5raW5nc191cCA8LSAgIHNhcHBseShjb2xuYW1lcyhkYXRhKSwgZnVuY3Rpb24oc2FtcGxlKSBnZXRSYW5raW5nKGRhdGEsIHNhbXBsZSwgc2lnbmF0dXJlZ2VuZXNfdXApKQogICAgICAgIHJhbmtpbmdzX2Rvd20gPC0gICBzYXBwbHkoY29sbmFtZXMoZGF0YSksIGZ1bmN0aW9uKHNhbXBsZSkgZ2V0UmFua2luZyhkYXRhLCBzYW1wbGUsIHNpZ25hdHVyZWdlbmVzX2Rvd24pKQogICAgICAgIAogICAgICAgIHJhbmtpbmdfZmluYWwgPC0gKHJhbmtpbmdzX3VwLXJhbmtpbmdzX2Rvd20pL2xlbmd0aCh1bml2ZXJzZV9nZW5lcykKICAgICAgICByYW5raW5nX2ZpbmFsIDwtIGRhdGEuZnJhbWUoc2FtcGxlPWNvbG5hbWVzKGRhdGEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY29yZT1yYW5raW5nX2ZpbmFsKQogICAgICAgIAogICAgICAgIAogICAgICAgIAogICAgICAgIAogICAgICB9IGVsc2UgeyAgIyBPbmx5IG9uZSBrbm93biBkaXJlY3Rpb24gKHVwIG9yIGRvd24pCiAgICAgICAgCiAgICAgICAgbWVzc2FnZShwYXN0ZTAoIkNvbnNpZGVyaW5nIHVuaWRpcmVjdGlvbmFsIGdlbmUgc2lnbmF0dXJlIG1vZGUgZm9yIHNpZ25hdHVyZSAiLCBzaWcpKQogICAgICAgIAogICAgICAgIHNpZ25hdHVyZWdlbmVzIDwtIHNpZ25hdHVyZVssMV0KICAgICAgICB0b3RhbG5iX2dlbmVzIDwtIHN1bShzaWduYXR1cmVnZW5lcyAlaW4lIHJvdy5uYW1lcyhkYXRhKSkKICAgICAgICAKICAgICAgICAjIEFwcGx5IGdldFJhbmtpbmcgZnVuY3Rpb24gdG8gZWFjaCBzYW1wbGUgKGNvbHVtbikgCiAgICAgICAgcmFua2luZ3MgPC0gICBzYXBwbHkoY29sbmFtZXMoZGF0YSksIGZ1bmN0aW9uKHNhbXBsZSkgZ2V0UmFua2luZyhkYXRhLCBzYW1wbGUsIHNpZ25hdHVyZWdlbmVzKSkKICAgICAgICAKICAgICAgICByYW5raW5nX2ZpbmFsIDwtIHJhbmtpbmdzL2xlbmd0aCh1bml2ZXJzZV9nZW5lcykKICAgICAgICByYW5raW5nX2ZpbmFsIDwtIGRhdGEuZnJhbWUoc2FtcGxlPWNvbG5hbWVzKGRhdGEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY29yZT1yYW5raW5nX2ZpbmFsKQogICAgICAgIAogICAgICAgIAogICAgICB9CiAgICB9IGVsc2UgeyAgIyBJZiB2ZWN0b3Igb2YgZ2VuZXMgKHVuaWRpcmVjdGlvbmFsKQogICAgICBtZXNzYWdlKHBhc3RlMCgiQ29uc2lkZXJpbmcgdW5pZGlyZWN0aW9uYWwgZ2VuZSBzaWduYXR1cmUgbW9kZSBmb3Igc2lnbmF0dXJlICIsIHNpZykpCiAgICAgIAogICAgICAKICAgICAgc2lnbmF0dXJlZ2VuZXMgPC0gc2lnbmF0dXJlIAogICAgICB0b3RhbG5iX2dlbmVzIDwtIHN1bShzaWduYXR1cmVnZW5lcyAlaW4lIHJvdy5uYW1lcyhkYXRhKSkKICAgICAgCiAgICAgICMgQXBwbHkgZ2V0UmFua2luZyBmdW5jdGlvbiB0byBlYWNoIHNhbXBsZSAoY29sdW1uKSAKICAgICAgcmFua2luZ3MgPC0gICBzYXBwbHkoY29sbmFtZXMoZGF0YSksIGZ1bmN0aW9uKHNhbXBsZSkgZ2V0UmFua2luZyhkYXRhLCBzYW1wbGUsIHNpZ25hdHVyZWdlbmVzKSkKICAgICAgCiAgICAgIHJhbmtpbmdfZmluYWwgPC0gcmFua2luZ3MvbGVuZ3RoKHVuaXZlcnNlX2dlbmVzKQogICAgICByYW5raW5nX2ZpbmFsIDwtIGRhdGEuZnJhbWUoc2FtcGxlPWNvbG5hbWVzKGRhdGEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NvcmU9cmFua2luZ19maW5hbCkKICAgICAgCiAgICB9CiAgICAKICAgIGlmICghaXMubnVsbChtZXRhZGF0YSkpIHJhbmtpbmdfZmluYWwgPC0gbWVyZ2UocmFua2luZ19maW5hbCwgbWV0YWRhdGEsIGJ5ID0gInNhbXBsZSIpCiAgICAKICAgIHJvdy5uYW1lcyhyYW5raW5nX2ZpbmFsKSA8LSBOVUxMCiAgICAKICAgIFJlc3VsdHNMaXN0W1tzaWddXSA8LSByYW5raW5nX2ZpbmFsCiAgICAKICB9CiAgCiAgcmV0dXJuKFJlc3VsdHNMaXN0KQp9CgpgYGAKCiAKLSBpbmNsdWRlIGluIHBsb3RTY29yZXMgdGhlIG5ldyBvcHRpb24gZm9yIHJhbmtpbmcKLSBpbmNsdWRlIGluIGNhbGN1bGF0ZVNjb3JlcyB0aGUgbmV3IG9wdGlvbiBmb3IgcmFua2luZwoKCmBgYHtyICBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD0xMH0KIApkZl9yYW5raW5nIDwtIENhbGN1bGF0ZVNjb3Jlc19SYW5raW5nKGNvcnJjb3VudHNfbWVyZ2UsIG1ldGFkYXRhX21lcmdlLGJpZGlyZWN0c2lncykKCgoKc2VuZXNjZW5jZV90cmlnZ2Vyc19jb2xvcnMgPC0gYygKICAibm9uZSIgPSAiI0U1NzM3MyIsICAjIFNvZnQgcmVkICAKICAiUmFkaWF0aW9uIiA9ICIjQkRCREJEIiwgICMgTWVkaXVtIGdyYXkgIAogICJETkEgZGFtYWdlIiA9ICIjNjRCNUY2IiwgICMgQnJpZ2h0ZXIgYmx1ZSAgCiAgIlRlbG9tZXJlIHNob3J0ZW5pbmciID0gIiM0RkMzRjciLCAgIyBWaXZpZCBza3kgYmx1ZSAgCiAgIkROQSBkZW1ldGh5bGF0aW9uIiA9ICIjQkE2OEM4IiwgICMgUmljaCBsYXZlbmRlciAgCiAgIk94aWRhdGl2ZSBzdHJlc3MiID0gIiNGREQ4MzUiLCAgIyBTdHJvbmcgeWVsbG93ICAKICAiQ29uZGl0aW9uZWQgTWVkaXVtIiA9ICIjRjI5OTRBIiwgICMgV2FybSBvcmFuZ2UgIAogICJPbmNvZ2VuZSIgPSAiIzgxQzc4NCIsICAjIE1lZGl1bSBncmVlbiAgCiAgIkxpcGlkIEFjY3VtdWxhdGlvbiIgPSAiI0U1NzM3MyIsICAjIENvcmFsICAKICAiQ2FsY2l1bSBpbmZsdXgiID0gIiMyNkE2OUEiLCAgIyBEZWVwIHRlYWwgIAogICJQbGFzbWEgbWVtYnJhbmUgZHlzcnVwdGlvbiIgPSAiI0QzMkYyRiIsICAjIFN0cm9uZyBzYWxtb24gIAogICJPU0tNIGZhY3RvcnMiID0gIiNGRkI3NEQiLCAgIyBCcmlnaHQgcGVhY2ggIAogICJZQVAgS08iID0gIiM5NTc1Q0QiICAjIERlZXAgcGFzdGVsIHB1cnBsZSAgCikKCmNlbGxUeXBlc19jb2xvcnMgPC0gYygKICAiRmlicm9ibGFzdCIgPSAiI0ZGNjk2MSIsICAgIyBTdHJvbmcgUGFzdGVsIFJlZCAgCiAgIktlcmF0aW5vY3l0ZSIgPSAiI0ZGQjM0NyIsICMgU3Ryb25nIFBhc3RlbCBPcmFuZ2UgIAogICJNZWxhbm9jeXRlIiA9ICIjRkZENzAwIiwgICAjIFN0cm9uZyBQYXN0ZWwgWWVsbG93ICAKICAiRW5kb3RoZWxpYWwiID0gIiM3N0RENzciLCAgIyBTdHJvbmcgUGFzdGVsIEdyZWVuICAKICAiTmV1cm9uYWwiID0gIiM3NzlFQ0IiLCAgICAgIyBTdHJvbmcgUGFzdGVsIEJsdWUgIAogICJNZXNlbmNoeW1hbCIgPSAiI0MyN0JBMCIgICAjIFN0cm9uZyBQYXN0ZWwgUHVycGxlICAKKQoKY29uZF9jb2hlbmQgPC0gbGlzdChBPWMoIlNlbmVzY2VudCIpLCAjIGlmIG5vIHZhcmlhYmxlIGlzIGRlZmluZWQsIHdpbGwgYmUgdGhlIGZpcnN0IHRoYXQgYXBwZWFycyBpbiB0aGUgZ2dwbG90CiAgICAgICAgICAgICAgICAgICAgQj1jKCJRdWllc2NlbnQiKSkKCm9wdGlvbnMoZXJyb3I9cmVjb3ZlcikKUGxvdFNjb3JlcyhSZXN1bHRzTGlzdCA9IGRmX3JhbmtpbmcsIENvbG9yVmFyaWFibGUgPSAiQ2VsbFR5cGUiLCBHcm91cGluZ1ZhcmlhYmxlPSJDb25kaXRpb24iLCAgbWV0aG9kID0ibG9nbWVkaWFuIiwgQ29sb3JWYWx1ZXMgPSBjZWxsVHlwZXNfY29sb3JzLCBDb25uZWN0R3JvdXBzPVRSVUUsIG5jb2wgPSAzLCBucm93ID0gMywgd2lkdGhUaXRsZT0yMCwgeV9saW1pdHMgPSBOVUxMLCBsZWdlbmRfbnJvdyA9IDIseGxhYj1OVUxMLCBjb25kX2NvaGVuZCA9IGNvbmRfY29oZW5kKQoKYGBgCgoKCiMgSW5kaXZpZHVhbCBHZW5lcwoKLSBQQ0Egd2l0aCBnZW5lcyBmcm9tIHNpZ25hdHVyZXMgb25seQoKIyMjIFZpb2xpbiBFeHByZXNzaW9uIFBsb3RzCgpgYGB7ciBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02fQoKCnNlbmVzY2VuY2VfdHJpZ2dlcnNfY29sb3JzIDwtIGMoCiAgIm5vbmUiID0gIiNFNTczNzMiLCAgIyBTb2Z0IHJlZCAgCiAgIlJhZGlhdGlvbiIgPSAiI0JEQkRCRCIsICAjIE1lZGl1bSBncmF5ICAKICAiRE5BIGRhbWFnZSIgPSAiIzY0QjVGNiIsICAjIEJyaWdodGVyIGJsdWUgIAogICJUZWxvbWVyZSBzaG9ydGVuaW5nIiA9ICIjNEZDM0Y3IiwgICMgVml2aWQgc2t5IGJsdWUgIAogICJETkEgZGVtZXRoeWxhdGlvbiIgPSAiI0JBNjhDOCIsICAjIFJpY2ggbGF2ZW5kZXIgIAogICJPeGlkYXRpdmUgc3RyZXNzIiA9ICIjRkREODM1IiwgICMgU3Ryb25nIHllbGxvdyAgCiAgIkNvbmRpdGlvbmVkIE1lZGl1bSIgPSAiI0YyOTk0QSIsICAjIFdhcm0gb3JhbmdlICAKICAiT25jb2dlbmUiID0gIiM4MUM3ODQiLCAgIyBNZWRpdW0gZ3JlZW4gIAogICJMaXBpZCBBY2N1bXVsYXRpb24iID0gIiNFNTczNzMiLCAgIyBDb3JhbCAgCiAgIkNhbGNpdW0gaW5mbHV4IiA9ICIjMjZBNjlBIiwgICMgRGVlcCB0ZWFsICAKICAiUGxhc21hIG1lbWJyYW5lIGR5c3J1cHRpb24iID0gIiNEMzJGMkYiLCAgIyBTdHJvbmcgc2FsbW9uICAKICAiT1NLTSBmYWN0b3JzIiA9ICIjRkZCNzREIiwgICMgQnJpZ2h0IHBlYWNoICAKICAiWUFQIEtPIiA9ICIjOTU3NUNEIiAgIyBEZWVwIHBhc3RlbCBwdXJwbGUgIAopCgoKSW5kaXZpZHVhbEdlbmVzX1Zpb2xpbnMoZGF0YSA9IGNvcnJjb3VudHNfbWVyZ2UsIG1ldGFkYXRhID0gbWV0YWRhdGFfbWVyZ2UsIGdlbmVzID0gYygiQ0RLTjFBIiwgIkNES04yQSIsICJHTEIxIiwiVFA1MyIsIkNDTDIiKSwgR3JvdXBpbmdWYXJpYWJsZSA9ICJDb25kaXRpb24iLCBwbG90PVQsIG5jb2w9TlVMTCwgbnJvdz0yLCBkaXZpZGU9IkNlbGxUeXBlIiwgaW52ZXJ0X2RpdmlkZT1GQUxTRSxDb2xvclZhbHVlcz1zZW5lc2NlbmNlX3RyaWdnZXJzX2NvbG9ycywgcG9pbnRTaXplPTIsIENvbG9yVmFyaWFibGU9IlNlbmVzY2VudFR5cGUiLCB0aXRsZT0iU2VuZXNjZW5jZSIsIHdpZHRoVGl0bGU9MTYseV9saW1pdHMgPSBOVUxMLGxlZ2VuZF9ucm93PTQsIHhsYWI9IkNvbmRpdGlvbiIsY29sb3JsYWI9IiIpIApgYGAKCgoKIyMjIENvcnJlbGF0aW9uIEhlYXRtYXAKCgpgYGB7ciBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00fQpvcHRpb25zKGVycm9yPXJlY292ZXIpCkNvcnJlbGF0aW9uSGVhdG1hcChkYXRhPWNvcnJjb3VudHNfbWVyZ2UsIAogICAgICAgICAgICAgICAgICAgbWV0YWRhdGEgPSBtZXRhZGF0YV9tZXJnZSwgCiAgICAgICAgICAgICAgICAgICBnZW5lcz1jKCJDREtOMUEiLCAiQ0RLTjJBIiwgIkdMQjEiLCJUUDUzIiwiQ0NMMiIpLCAKICAgICAgICAgICAgICAgICAgIHNlcGFyYXRlLmJ5ID0gIkNvbmRpdGlvbiIsIAogICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInBlYXJzb24iLCAgCiAgICAgICAgICAgICAgICAgICBjb2xvcmxpc3QgPSBsaXN0KGxvdyA9ICIjM0Y0MTkzIiwgbWlkID0gIiNGOUY0QUUiLCBoaWdoID0gIiNCNDQxNDEiKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0c19jb2xvcnNjYWxlID0gYygtMSwwLDEpLCAKICAgICAgICAgICAgICAgICAgIHdpZHRoVGl0bGUgPSAxNiwgCiAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICJ0ZXN0IiwgCiAgICAgICAgICAgICAgICAgICBjbHVzdGVyX3Jvd3MgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJfY29sdW1ucyA9IFRSVUUsICAKICAgICAgICAgICAgICAgICAgIGRldGFpbGVkcmVzdWx0cyA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgIGxlZ2VuZF9wb3NpdGlvbj0icmlnaHQiLAogICAgICAgICAgICAgICAgICAgdGl0bGVzaXplPTIwKQoKCmBgYAoKCgoKIyMjIEV4cHJlc3Npb24gSGVhdG1hcHMKCmBgYHtyIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD00fQpvcHRpb25zKGVycm9yPXJlY292ZXIpCgphbm5vdGF0aW9uX2NvbG9ycyA8LSBsaXN0KAogIENlbGxUeXBlID0gYygKICAgICJGaWJyb2JsYXN0IiAgID0gIiNGRjY5NjEiLCAgICMgU3Ryb25nIFBhc3RlbCBSZWQgIAogICAgIktlcmF0aW5vY3l0ZSIgPSAiI0ZGQjM0NyIsICAgIyBTdHJvbmcgUGFzdGVsIE9yYW5nZSAgCiAgICAiTWVsYW5vY3l0ZSIgICA9ICIjRkZENzAwIiwgICAjIFN0cm9uZyBQYXN0ZWwgWWVsbG93ICAKICAgICJFbmRvdGhlbGlhbCIgID0gIiM3N0RENzciLCAgICMgU3Ryb25nIFBhc3RlbCBHcmVlbiAgCiAgICAiTmV1cm9uYWwiICAgICA9ICIjNzc5RUNCIiwgICAjIFN0cm9uZyBQYXN0ZWwgQmx1ZSAgCiAgICAiTWVzZW5jaHltYWwiICA9ICIjQzI3QkEwIiAgICAjIFN0cm9uZyBQYXN0ZWwgUHVycGxlICAKICApLAogIENvbmRpdGlvbiA9IGMoCiAgICAiU2VuZXNjZW50IiAgICAgPSAiIzY1QUM3QyIsICAjIEV4YW1wbGUgY29sb3I6IGdyZWVuaXNoCiAgICAiUHJvbGlmZXJhdGl2ZSIgPSAiIzVGOTBENCIsICAjIEV4YW1wbGUgY29sb3I6IGJsdWVpc2gKICAgICJRdWllc2NlbnQiICAgICA9ICIjRURBMDNFIiAgICMgRXhhbXBsZSBjb2xvcjogb3JhbmdlCiAgKQopCgpFeHByZXNzaW9uSGVhdG1hcChkYXRhPWNvcnJjb3VudHNfbWVyZ2UsIAogICAgICAgICAgICAgICAgICBtZXRhZGF0YSA9IG1ldGFkYXRhX21lcmdlLCAKICAgICAgICAgICAgICAgICAgZ2VuZXM9YygiQ0RLTjFBIiwgIkNES04yQSIsICJHTEIxIiwiVFA1MyIsIkNDTDIiKSwgIAogICAgICAgICAgICAgICAgICBhbm5vdGF0ZS5ieSA9IGMoIkNlbGxUeXBlIiwiQ29uZGl0aW9uIiksCiAgICAgICAgICAgICAgICAgIGFubm90YXRpb25fY29sb3JzID0gYW5ub3RhdGlvbl9jb2xvcnMsCiAgICAgICAgICAgICAgICAgIGNvbG9ybGlzdCA9IGxpc3QobG93ID0gIiMzRjQxOTMiLCBtaWQgPSAiI0Y5RjRBRSIsIGhpZ2ggPSAiI0I0NDE0MSIpLAogICAgICAgICAgICAgICAgICBjbHVzdGVyX3Jvd3MgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9jb2x1bW5zID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgIHRpdGxlID0gInRlc3QiLCAKICAgICAgICAgICAgICAgICAgdGl0bGVzaXplID0gMjAsCiAgICAgICAgICAgICAgICAgIGxlZ2VuZF9wb3NpdGlvbiA9ICJyaWdodCIsCiAgICAgICAgICAgICAgICAgIHNjYWxlX3Bvc2l0aW9uPSJyaWdodCIpCgpgYGAKCgoKIyMjIFJPQy9BVUMgCgpgYGB7ciBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NH0KCmNlbGxUeXBlc19jb2xvcnMgPC0gYygKICAiRmlicm9ibGFzdCIgPSAiI0ZGNjk2MSIsICAgIyBTdHJvbmcgUGFzdGVsIFJlZCAgCiAgIktlcmF0aW5vY3l0ZSIgPSAiI0ZGQjM0NyIsICMgU3Ryb25nIFBhc3RlbCBPcmFuZ2UgIAogICJNZWxhbm9jeXRlIiA9ICIjRkZENzAwIiwgICAjIFN0cm9uZyBQYXN0ZWwgWWVsbG93ICAKICAiRW5kb3RoZWxpYWwiID0gIiM3N0RENzciLCAgIyBTdHJvbmcgUGFzdGVsIEdyZWVuICAKICAiTmV1cm9uYWwiID0gIiM3NzlFQ0IiLCAgICAgIyBTdHJvbmcgUGFzdGVsIEJsdWUgIAogICJNZXNlbmNoeW1hbCIgPSAiI0MyN0JBMCIgICAjIFN0cm9uZyBQYXN0ZWwgUHVycGxlICAKKQoKUk9DYW5kQVVDcGxvdChjb3JyY291bnRzX21lcmdlLCAKICAgICAgICAgICAgICBtZXRhZGF0YV9tZXJnZSwgCiAgICAgICAgICAgICAgY29uZGl0aW9uX3ZhciA9ICJDb25kaXRpb24iLCAKICAgICAgICAgICAgICBjbGFzcyA9ICJTZW5lc2NlbnQiLCAKICAgICAgICAgICAgICBnZW5lcz1jKCJDREtOMUEiLCAiQ0RLTjJBIiwgIkdMQjEiLCJUUDUzIiwiQ0NMMiIpLCAKICAgICAgICAgICAgICBncm91cF92YXI9IkNlbGxUeXBlIiwKICAgICAgICAgICAgICBwbG90X3R5cGUgPSAiYWxsIiwKICAgICAgICAgICAgICBoZWF0bWFwX3BhcmFtcyA9IGxpc3QoY29sID0gbGlzdCggIiNGOUY0QUUiICwiI0I0NDE0MSIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAuNSwxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9yb3dzPVQpLAogICAgICAgICAgICAgIHJvY19wYXJhbXMgPSBsaXN0KG5yb3c9MiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sPTIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3JzPWNlbGxUeXBlc19jb2xvcnMpLAogICAgICAgICAgICAgIGNvbW1vbXBsb3RfcGFyYW1zID0gbGlzdCh3aWR0aHM9YygwLjUsMC41KSkpCgoKYGBgCgojIyMgQ29oZW4ncyBkCgpgYGB7cn0KQ29oZW5ESGVhdG1hcChjb3JyY291bnRzX21lcmdlLCAKICAgICAgICAgICAgICBtZXRhZGF0YV9tZXJnZSwgCiAgICAgICAgICAgICAgZ2VuZXM9YygiQ0RLTjFBIiwgIkNES04yQSIsICJHTEIxIiwiVFA1MyIsIkNDTDIiKSwKICAgICAgICAgICAgICBjb25kaXRpb25fdmFyID0gIkNvbmRpdGlvbiIsIAogICAgICAgICAgICAgIGNsYXNzID0gIlNlbmVzY2VudCIsIAogICAgICAgICAgICAgIGdyb3VwX3ZhciA9ICJDZWxsVHlwZSIsCiAgICAgICAgICAgICAgdGl0bGUgPSBOVUxMLAogICAgICAgICAgICAgIHdpZHRoVGl0bGUgPSAxNiwKICAgICAgICAgICAgICBoZWF0bWFwX3BhcmFtcyA9IGxpc3QoY29sID0gbGlzdCggIiNGOUY0QUUiICwiI0I0NDE0MSIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBOVUxMLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyX3Jvd3M9VCkpCmBgYAoKIyMjIFBDQSB3aXRoIGdlbmVzIGZyb20gc2lnbmF0dXJlIG9ubHkKCmBgYHtyIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CgpDZWxsVHlwZWNvbHMgPSBjKAogICJGaWJyb2JsYXN0IiAgID0gIiNGRjY5NjEiLCAgICMgU3Ryb25nIFBhc3RlbCBSZWQgIAogICJLZXJhdGlub2N5dGUiID0gIiNGRkIzNDciLCAgICMgU3Ryb25nIFBhc3RlbCBPcmFuZ2UgIAogICJNZWxhbm9jeXRlIiAgID0gIiNGRkQ3MDAiLCAgICMgU3Ryb25nIFBhc3RlbCBZZWxsb3cgIAogICJFbmRvdGhlbGlhbCIgID0gIiM3N0RENzciLCAgICMgU3Ryb25nIFBhc3RlbCBHcmVlbiAgCiAgIk5ldXJvbmFsIiAgICAgPSAiIzc3OUVDQiIsICAgIyBTdHJvbmcgUGFzdGVsIEJsdWUgIAogICJNZXNlbmNoeW1hbCIgID0gIiNDMjdCQTAiICAgICMgU3Ryb25nIFBhc3RlbCBQdXJwbGUgIAopCgpzZW5jb2xzIDwtIGMoCiAgIlNlbmVzY2VudCIgPSAiI0QzMkYyRiIsICAjIFN0cm9uZyBzYWxtb24gIAogICJRdWllc2NlbnQiID0gIiNGRkI3NEQiLCAgIyBCcmlnaHQgcGVhY2ggIAogICJQcm9saWZlcmF0aXZlIiA9ICIjOTU3NUNEIiAgIyBEZWVwIHBhc3RlbCBwdXJwbGUgIAopCgpwbG90UENBKGRhdGE9Y29ycmNvdW50c19tZXJnZSwgCiAgICAgICAgbWV0YWRhdGE9bWV0YWRhdGFfbWVyZ2UsIAogICAgICAgIGdlbmVzPWMoIkNES04xQSIsICJDREtOMkEiLCAiR0xCMSIsIlRQNTMiLCJDQ0wyIiksIAogICAgICAgIHNjYWxlPUZBTFNFLCAKICAgICAgICBjZW50ZXI9VFJVRSwgCiAgICAgICAgUENzPWxpc3QoYygxLDIpLCBjKDIsMyksIGMoMyw0KSksIAogICAgICAgIENvbG9yVmFyaWFibGU9IkNvbmRpdGlvbiIsCiAgICAgICAgQ29sb3JWYWx1ZXM9c2VuY29scywKICAgICAgICBwb2ludFNpemU9NSwKICAgICAgICBsZWdlbmRfbnJvdz0xLCAKICAgICAgICBuY29sPTMsIAogICAgICAgIG5yb3c9TlVMTCkKYGBgCgpgYGB7cn0KCiMnIEBpbXBvcnRGcm9tIGVkZ2VSIERHRUxpc3QKIycgQGltcG9ydEZyb20gc3RhdHMgcHJjb21wCiMnIEBpbXBvcnQgZ2dwbG90MgojJyBAaW1wb3J0RnJvbSBnZ3B1YnIgZ2dhcnJhbmdlCgoKYGBgCgoKCg==